Skip to content

Commit

Permalink
fixed some TODOs
Browse files Browse the repository at this point in the history
- added timeouts to backend put methods
- removed sleep on connect
- robo testing
  • Loading branch information
Peter Wegmann committed Nov 2, 2023
1 parent f6279af commit 8903e22
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 39 deletions.
191 changes: 175 additions & 16 deletions jupyter/robo-testing.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "code",
"execution_count": null,
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -19,6 +19,8 @@
"\n",
"\n",
"\n",
"\n",
"\n",
"# Create a run engine and a temporary file backed database. Send all the documents from the RE into that database\n",
"RE = RunEngine({})\n",
"db = temp()\n",
Expand Down Expand Up @@ -115,25 +117,55 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 2,
"metadata": {},
"outputs": [],
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1\n",
"2\n",
"3\n",
"4\n",
"5\n",
"6\n"
]
},
{
"data": {
"text/plain": [
"()"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def load_samples(storage, lower, upper):\n",
" loadshort:SECoP_CMD_Device = storage.load_short_dev\n",
"\n",
"loadshort:SECoP_CMD_Device = storage.load_short_dev\n",
" for samplepos in range(lower,upper):\n",
" #await asyncio.sleep(2)\n",
" yield from bps.abs_set(loadshort.samplepos_arg,samplepos, group='sample')\n",
" yield from bps.abs_set(loadshort.substance_arg,random.randint(0,6), group='sample') \n",
"\n",
"for samplepos in range(1,7):\n",
" #await asyncio.sleep(2)\n",
" await loadshort.samplepos_arg.set(samplepos)\n",
" await loadshort.substance_arg.set(random.randint(0,6))\n",
" yield from bps.wait('sample')\n",
" \n",
" \n",
" # Execute load short command\n",
" yield from bps.trigger(loadshort,wait=True)\n",
"\n",
" await loadshort.load_short_x.execute()\n",
"\n",
" await storage.wait_for_IDLE()\n",
" await robot.wait_for_IDLE()\n",
" #await asyncio.sleep(2)\n",
" print(samplepos)\n",
" \n",
"\n",
" #yield from bps.wait_for([storage.wait_for_IDLE,robot.wait_for_IDLE])\n",
"\n",
" print(samplepos)\n",
"\n",
"\n",
"RE(load_samples(storage,1,7))\n",
" "
]
},
Expand Down Expand Up @@ -229,7 +261,134 @@
" yield from bps.mv(sample,1)\n",
" yield from bps.mv(sample,0)\n",
" \n",
"RE(dumb(sample))"
"RE(dumb(sample))\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"()"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"async def test_except():\n",
" await asyncio.sleep(1)\n",
" raise RuntimeError\n",
" \n",
" \n",
"def except_plan():\n",
" yield from bps.wait_for([test_except])\n",
" \n",
" \n",
"RE(except_plan())"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"({<Task finished name='Task-53' coro=<test_except() done, defined at /tmp/ipykernel_47612/3142971814.py:1> exception=RuntimeError()>},\n",
" set())"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"fut = asyncio.ensure_future(test_except())\n",
"\n",
"await asyncio.wait([fut])\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"ename": "AttributeError",
"evalue": "primary",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)",
"File \u001b[0;32m~/git-repos/secop-ophyd/.venv/lib64/python3.11/site-packages/intake/catalog/base.py:350\u001b[0m, in \u001b[0;36mCatalog.__getattr__\u001b[0;34m(self, item)\u001b[0m\n\u001b[1;32m 349\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[0;32m--> 350\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mself\u001b[39;49m[item] \u001b[39m# triggers reload_on_change\u001b[39;00m\n\u001b[1;32m 351\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mKeyError\u001b[39;00m \u001b[39mas\u001b[39;00m e:\n",
"File \u001b[0;32m~/git-repos/secop-ophyd/.venv/lib64/python3.11/site-packages/intake/catalog/base.py:423\u001b[0m, in \u001b[0;36mCatalog.__getitem__\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 422\u001b[0m \u001b[39mreturn\u001b[39;00m out()\n\u001b[0;32m--> 423\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mKeyError\u001b[39;00m(key)\n",
"\u001b[0;31mKeyError\u001b[0m: 'primary'",
"\nThe above exception was the direct cause of the following exception:\n",
"\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m/home/peter/git-repos/secop-ophyd/jupyter/robo-testing.ipynb Cell 13\u001b[0m line \u001b[0;36m4\n\u001b[1;32m <a href='vscode-notebook-cell:/home/peter/git-repos/secop-ophyd/jupyter/robo-testing.ipynb#X15sZmlsZQ%3D%3D?line=44'>45</a>\u001b[0m RE(measure(sample,i))\n\u001b[1;32m <a href='vscode-notebook-cell:/home/peter/git-repos/secop-ophyd/jupyter/robo-testing.ipynb#X15sZmlsZQ%3D%3D?line=47'>48</a>\u001b[0m run\u001b[39m=\u001b[39mdb[\u001b[39m-\u001b[39m\u001b[39m1\u001b[39m]\n\u001b[0;32m---> <a href='vscode-notebook-cell:/home/peter/git-repos/secop-ophyd/jupyter/robo-testing.ipynb#X15sZmlsZQ%3D%3D?line=48'>49</a>\u001b[0m run\u001b[39m.\u001b[39;49mprimary\u001b[39m.\u001b[39mread()\n",
"File \u001b[0;32m~/git-repos/secop-ophyd/.venv/lib64/python3.11/site-packages/intake/catalog/base.py:352\u001b[0m, in \u001b[0;36mCatalog.__getattr__\u001b[0;34m(self, item)\u001b[0m\n\u001b[1;32m 350\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mself\u001b[39m[item] \u001b[39m# triggers reload_on_change\u001b[39;00m\n\u001b[1;32m 351\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mKeyError\u001b[39;00m \u001b[39mas\u001b[39;00m e:\n\u001b[0;32m--> 352\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mAttributeError\u001b[39;00m(item) \u001b[39mfrom\u001b[39;00m \u001b[39me\u001b[39;00m\n\u001b[1;32m 353\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mAttributeError\u001b[39;00m(item)\n",
"\u001b[0;31mAttributeError\u001b[0m: primary"
]
}
],
"source": [
"def measure(sample,sample_num):\n",
" \n",
"\n",
" \n",
" reading = yield from bps.read(sample)\n",
" \n",
" \n",
" curr_sample = reading[sample.value.name]['value']\n",
" \n",
" # holding wrong sample --> put it back into storage\n",
" if curr_sample != 0 and curr_sample != sample_num :\n",
" yield from bps.mv(sample,0)\n",
" \n",
" # gripper empty --> grab correct sample\n",
" if curr_sample == 0:\n",
" yield from bps.mv(sample,i)\n",
" \n",
" # Do actual measurement\n",
"\n",
" @bpp.run_decorator()\n",
" def inner_meas(sample):\n",
"\n",
" complete_status = yield from bps.complete(sample.measure_dev, wait=False) #This message doesn't exist yet\n",
" \n",
" # While the device is still executing, read from the detectors in the detectors list\n",
" while not complete_status.done:\n",
"\n",
" yield Msg('checkpoint') # allows us to pause the run \n",
" \n",
" yield Msg('sleep', None, 1) \n",
" \n",
" uid = yield from inner_meas(sample)\n",
"\n",
" \n",
" # put sample back into storage\n",
" yield from bps.mv(sample,0)\n",
"\n",
" return uid\n",
"\n",
"\n",
"\n",
"\n",
"for i in range(1,7):\n",
" #grab sample i and hold in Measurement Pos\n",
" RE(measure(sample,i))\n",
" \n",
"\n",
"run=db[-1]\n"
]
}
],
Expand All @@ -249,7 +408,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.5"
"version": "3.11.6"
},
"orig_nbformat": 4
},
Expand Down
2 changes: 0 additions & 2 deletions secop_ophyd/AsyncFrappyClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,6 @@ async def create(cls, host, port, loop, log=Logger):
async def connect(self, try_period=0):
await asyncio.to_thread(self.client.connect, try_period)
self.conn_timestamp = time.time()
# TODO find better solution than sleep,somehow it is needed
await asyncio.sleep(1)

async def disconnect(self, shutdown=True):
await asyncio.to_thread(self.client.disconnect, shutdown)
Expand Down
10 changes: 5 additions & 5 deletions secop_ophyd/SECoPDevices.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
Stoppable,
SyncOrAsync,
Flyable,
Triggerable,
)

from ophyd_async.core.standard_readable import StandardReadable
Expand Down Expand Up @@ -357,7 +358,6 @@ async def _move(self, new_target):
async def stop(self, success=True) -> SyncOrAsync[None]:
self._success = success

traceback.print_stack()
await self._secclient.execCommand(self._module, "stop")
self._stopped = True

Expand Down Expand Up @@ -439,7 +439,7 @@ def __init__(self, path: Path, secclient: AsyncFrappyClient):
super().__init__(name=dev_name)


class SECoP_CMD_Device(StandardReadable, Flyable):
class SECoP_CMD_Device(StandardReadable, Flyable, Triggerable):
"""
Command devices that have Signals for command args, return values and a signal
for triggering command execution
Expand Down Expand Up @@ -578,9 +578,9 @@ def collect(self) -> Iterator[PartialEvent]:
async def describe_collect(self) -> SyncOrAsync[Dict[str, Dict[str, Descriptor]]]:
return await self.describe()


class SECoP_ArrayOf_XDevice(StandardReadable):
pass
def trigger(self) -> Status:
coro = asyncio.wait_for(fut=self._exec_cmd(), timeout=None)
return AsyncStatus(awaitable=coro, watchers=None)


class SECoP_Node_Device(StandardReadable):
Expand Down
30 changes: 19 additions & 11 deletions secop_ophyd/SECoPSignal.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,14 @@ async def put(self, value: Any | None, wait=True, timeout=None):
argument = arg_datatype.export_datatype(await sig.get_value())

# Run SECoP Command
res, qualifiers = await self._secclient.execCommand(
module=self.path._module_name,
command=self.path._accessible_name,
argument=argument,

res, qualifiers = await asyncio.wait_for(
fut=self._secclient.execCommand(
module=self.path._module_name,
command=self.path._accessible_name,
argument=argument,
),
timeout=timeout,
)

# write return Value to corresponding Backends
Expand Down Expand Up @@ -294,17 +298,20 @@ async def connect(self):
pass

async def put(self, value: Any | None, wait=True, timeout=None):
# TODO wait + timeout

# top level nested datatypes (handled as sting Signals)

if self.path._dev_path == []:
if self.SECoPdtype == "tuple":
value = self.SECoPdtype_obj.from_string(value)

if self.SECoPdtype == "struct":
value = self.SECoPdtype_obj.from_string(value)

await self._secclient.setParameter(**self.get_param_path(), value=value)
await asyncio.wait_for(
self._secclient.setParameter(**self.get_param_path(), value=value),
timeout=timeout,
)

return

# signal sub element of SECoP parameter (tuple or struct member)
Expand All @@ -320,7 +327,10 @@ async def put(self, value: Any | None, wait=True, timeout=None):
new_val = self.path.insert_val(curr_val, value)

# set new value
await self._secclient.setParameter(**self.get_param_path(), value=new_val)
await asyncio.wait_for(
fut=self._secclient.setParameter(**self.get_param_path(), value=new_val),
timeout=timeout,
)

async def get_descriptor(self) -> Descriptor:
res = {}
Expand Down Expand Up @@ -440,7 +450,7 @@ def _set_dtype(self) -> None:


class PropertyBackend(SignalBackend):
"""A read/write/monitor backend for a Signals"""
"""read backend for a SECoP Properties"""

def __init__(
self, prop_key: str, propertyDict: Dict[str, T], secclient: AsyncFrappyClient
Expand Down Expand Up @@ -517,8 +527,6 @@ def __init__(self, prefix, name, module_name, param_desc, secclient, kind) -> No

# TODO: Array: shape for now only for the first Dim, later maybe recursive??

# TODO: status tuple


# Tuple and struct are handled in a special way. They are unfolded into subdevices

Expand Down
13 changes: 8 additions & 5 deletions test/test_async_frappy_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,20 @@ async def test_async_secopclient_reconn(

await async_frappy_client.disconnect(False)

assert async_frappy_client.state == "reconnecting"
# for a short period the status is still "connected" (the disconn task finishes and the state is only set to a new value once the reconnect thread starts)
while async_frappy_client.state == "connected":
await asyncio.sleep(0.001)

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

assert async_frappy_client.state == "connected"

# ensures we are connected and getting fresh data again
reading1 = await async_frappy_client.getParameter("cryo", "value", False)
reading2 = await async_frappy_client.getParameter("cryo", "value", False)
reading3 = await async_frappy_client.getParameter("cryo", "value", False)
reading4 = await async_frappy_client.getParameter("cryo", "value", False)

assert reading1.get_value() != reading2.get_value()
assert reading3.get_value() != reading4.get_value()

await async_frappy_client.disconnect(True)

Expand Down

0 comments on commit 8903e22

Please sign in to comment.