How to interact with signals while implementing bluesky verbs#

To implement bluesky verbs, you typically need to interact with Signals. This guide will show you how to do the following operations on a Signal:

  • Get the value

  • Set the value

  • Observe every value change

  • Wait for the value to match some expected value

Get the value#

To get a single signal value, you can call SignalR.get_value, awaiting the response:

value = await signal.get_value()

You can wrap these with asyncio.gather if you need to get multiple values at the same time:

value1, value2 = await asyncio.gather(signal1.get_value(), signal2.get_value())

Set the value#

To set a single value and wait for it to complete (for up to a timeout) you can call SignalW.set:

await signal.set(value, timeout)

If you want to do something else while waiting for it to complete you can store the signal it returns and await it later:

status = signal.set(value, timeout)
# do something else here
await status

Rarely there are operations (like telling an EPICS motor to stop) where you have to tell the control system not to wait for the operation to complete, otherwise it will deadlock with the operation that started it moving in the first place:

await stop_signal.set(value, wait=False)

Observe every value change#

To observe every value change and run a function on that value you can use observe_value:

async for value in observe_value(signal):
    do_something_with(value)

This will run until you break out of the loop. If you would like to break out of the loop when some other operation is complete you can pass an AsyncStatus that you create yourself or from the result of setting a signal, which will break out of the loop:

status = signal.set(value)
async for value in observe_value(signal2, done_status=status):
    do_something_with(value)
    # when signal.set() completes the loop will break out here

You can pass timeout to specify how the maximum time to wait for a single update, and done_timeout to specify the maximum time to wait for done_status.

If you want to wait for multiple signals you can use observe_signals_value:

async for signal, value in observe_value(signal1, signal2):
    if signal is signal1:
        do_something_with(value)
    if signal is signal2:
        do_something_else_with(value)

Wait for the value to match some expected value#

If you don’t need to run code for every signal update, but just want to wait until the signal matches some expected value, you can use wait_for_value:

await wait_for_value(device.acquiring, 1, timeout=1)

Or you can pass a function that returns True if the value matches a given condition:

await wait_for_value(device.num_captured, lambda v: v > 45, timeout=1)

Which you can use to implement a tolerance check using numpy.isclose:

await wait_for_value(device.temperature, lambda v: numpy.isclose(v, 32.79, atol=0.01), timeout=1)

Some control systems (like some EPICS StreamDevice implementations) return immediately when a signal is set, and require you to wait for that signal to match the value to know when it is complete. You can use set_and_wait_for_value and set_and_wait_for_other_value to do this:

await set_and_wait_for_value(signal, value)

Or if you have to wait for another signal to match a value:

await set_and_wait_for_other_value(signal1, value1, signal2, value2)

You can pass timeout to specify how long to wait for signal2 to match value2, and set_timeout to specify how long to wait

Some control systems (like EPICS areaDetector) return when an operation is complete, rather than when the operation has started. To support this you can ask not to wait_for_set_completion, just wait until the signal reaches the required value:

arm_status = await set_and_wait_for_value(driver.acquiring, 1, wait_for_set_completion=False)
# the detector is now armed, do something else
await arm_status
# the detector is now disarmed

This is better illustrated with a diagram:

eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1aa2/bxlx1MDAxMv3uXyGoX+vNvlx1MDAxZlx1MDAwNYqL2m5jO45rR8lccuyLXCKgpbXMilwiXHUwMDE5kvIjRf57ZylFS1GiZMdSol5UXHUwMDAy9NhZ7s7uzpk5M+RfO61Wu3hIbfunVtved4Mo7GXBXftH135rszxMYlx1MDAxMNHyf56Msm7Z86Yo0vynXHUwMDE3L4ZBNrBFXHUwMDFhXHUwMDA1XYtuw3xcdTAwMTREeTHqhVx06ibDXHUwMDE3YWGH+X/c52kwtD+nybBXZMhPsmt7YZFk47lsZIc2LnJcdTAwMTj9f/C/1fqr/KxoXHUwMDE3ZFkyVqxs9spJweqtp0lcXCrKMJOKcU2mXHUwMDFkwvxcdTAwMDCmKmxcdTAwMGak16Cu9Vx1MDAxMtfUfp3SU/z27lbfyd1cdTAwMTM+XHUwMDE4vOlcdTAwMWZ9wH7W6zCKOsVDVGqUJ7BcdTAwMTAvy4ssXHUwMDE52Pdhr7j5smWV9qarsmTUv4lt7lbu1UzSoFx1MDAxYlx1MDAxNlx1MDAwZq5ccuNpa1x1MDAxMPfLMXzLPfxTmiNqXHUwMDE4lkpcdTAwMTmtXHJcdTAwMTFT6fh6Q1x1MDAxMTRcbsxcdTAwMTSjWuuaXvtJXHUwMDA0Z1x1MDAwMHr9QKx7e82ugu6gXHUwMDBm6sW9aZ9cIlx1MDAwYuI8XHIyOCnf726yYkKQYJpcdTAwMTEsMcGEXHUwMDFh41x1MDAxNbmxYf+mcH2EQUIoxoRUWmIupVfHlodCXHTBSmpG/aE6JdKjXmlcdTAwMWJ/+KPIwKqO3CXxKIqq+1x1MDAxOfcm+/nFhrxcdTAwMTXRSctnv0rX/9eK9flcdTAwMTlGaS9cdTAwMThbXG5RTCliuCSaeb2iMFx1MDAxZdSnj5LuYIFx5UWQXHUwMDE1e2HcXHUwMDBi4379XHUwMDEyXHUwMDFi97ykovJcdTAwMDRcdTAwMTLlXHUwMDEy27/t37zrvLzg9+nlSXxE7y6SkehPz8BtR9JcdTAwMWQ5/THChlx1MDAwYmxcdTAwMTRhRrnzMKbSq1x1MDAxZqRuOXO7XHUwMDEwXHUwMDA1ebGfXGaHYVx1MDAwMVx1MDAwYj5Lwrioa1mu4Fx1MDAxN4fBXHUwMDFiXHUwMDFizO06rKEqq4M1dSN6bLuX/9Xy9lxc/pn+/uPHhb2b7ayUzlmYXHUwMDFmb6f6PVn8nJ8p7H2xyM1cYmrqrVM3Q4WgXGbjx7uZXfvxsHjJXlx1MDAxZqvLo0J3wpOzM3a23W5GY4OU4IYxLsGlKr9cdTAwMWLl9UQrZGCPXGbF0kgwvs35XHUwMDE5hpFkRivFXGJoI1xmn/cz1PueiV8hTEqsuJZ+9V/tV2ZcdTAwMDRPcSCCgIP2qj3BgVxcJ3HRXHQ/lT5cZs+0/lx1MDAxNlxmw+hh5lhLXHUwMDEzhm18s/ff1uug6N7YiiU44S9R2Hc23Y7s9ayxXHUwMDE3IYTnqbhIUi/twmRBXHUwMDE422x+c5Is7IdxXHUwMDEwvV02MazWXHUwMDFlTlx1MDAwM1x1MDAwMapcdTAwMWPRVZBbJ3Xt+qvAXHSG0Fx1MDAwNE6iqYG4J/x0q8B58bL77uH8z+vfR5+O9jvnx/dnr4Zvtlx1MDAxYpySXHUwMDEyXHUwMDA0sFx1MDAxNIpjKjxcdTAwMWXGXHUwMDE3c4awMpRcdTAwMTHtYEM3x1x1MDAwMIxCwlx1MDAwMFx1MDAwN+GYMYpcdTAwMTnx619cdTAwMDZMoTCGmKW95P9cdTAwMWWYXHUwMDFkW7RywFhcdTAwMTB9W1xcLpp3o7CkpN7qYSkgilCl1KNx+SY+4KMjcZLln+jZK315/CpcdTAwMTidbzcuXHQhXHUwMDEygVx1MDAwN2KOPVx1MDAxYVxm9HFcdTAwMTabVCikXGawXHUwMDE17uCim4OmXHUwMDA1OSPPgCbEbsCmVJhcdTAwMDNpXHUwMDEyclx1MDAwMTT5PDQpkCiiXHUwMDA09We0IWhOOZrnv9NjT49cdTAwMGav0oP3p4f4XHUwMDEwv/3z+nL/pdr7WKG+rXqOOJV8/kL4Vlx1MDAwMF9WwfBcdTAwMTXAZ3KmdVx08Mm3xTt5XCLMXHUwMDE5/SqYU9VIjVx1MDAwMVwijEHwNY/nxstTna2EudJcZjEuhGRMaUKUn3mCcoY4gVxiXHUwMDA3lJVTXFyxt3XDnFxuoMZcdTAwMTT2XFxpTpSRlXR/XHTOjVx1MDAwMWZcZlpvnFx1MDAxYS+D+fLKy4xcdTAwMWRuPczpt4U5XS/My15cdTAwMGJgXHUwMDBlQaxcdTAwMTHmlGDg2KSSI6+C+SCmcmD6/cPw4cPgIcxuLq/O1XbDXHUwMDFj6DPSXG4zXHJpJ5ZC1CttiiNBpISMXHUwMDE0YqzmqqbY+og2ZLKIcFxuXHUwMDBlZfy5IJorSFxifFx1MDAwN/isg55cdTAwMDI1UUBcdNT35N1lRY1XrXftXHUwMDE1tdnB1lvnmpGtt8iFKaqenjbVy8HLa844ODWJNbyp3SX8eUPuSoNM9bVyPM45qlqYr4yW49VcZpDJZ45HnjpeI0hcdTAwMTZcdTAwMGWntVx1MDAxZm+n+v1kMlTh1jUvXHRcdTAwMDFZSlwiXHUwMDFm7yOTbHTI3lx1MDAxZqvT24POMLzat6f7+1x1MDAwN1vuIyl3dUBphJFM8Vxup1x1MDAxOJdcdCmYmYBQ4UqJnLNmKvTsMiEty1x1MDAxZVpygYFcdTAwMDA8slx1MDAxYeEqm1xmvPfGM55Nk5XHXHUwMDE3I8pC3XcpRyyeeVx1MDAxZFx1MDAwNYkmXG4jmKq3TilcZqNcdTAwMDZLxeXjXHUwMDBihZfvzkn3WI8+MXGU60CLLJCDLYcnx0hgyiB+XHUwMDEwolx1MDAwNff5wVx1MDAxOJ5cdTAwMTJ8sIAkRVx1MDAwMYaVrtw4XTuFYTWCsprC+C5cdTAwMTOwSvC1IPqulcN/XHUwMDE5TFx1MDAxM93gTcdXSlx1MDAxMURwLTXsISVcdTAwMTBcdTAwMTMxXHUwMDAzXG7DVo85S2GUro5Zp1x1MDAxY5SuXHUwMDFhj1O8bFx1MDAwMLZSofpcdTAwMDCkpp9azVKacDBcdTAwMWXvuSyl8alcdEhjmjwhxVx1MDAxMJole4onXFxeo9tKT1xiXHSrQFxmU8nBJzJcdTAwMDFZWy2bk1xmXHUwMDE5ilx1MDAxOYWdMFxubHVjrpBSXHUwMDA0xFBriilQI6BMXHUwMDBimFxuMVxmXHUwMDAx5YeTMZIzUimnTW+jSKIoN1x1MDAxNXa5Xc9NbNZHNj03sfyeQcs/N7FLXHUwMDEwJFSSXHUwMDEyMFx1MDAwNlx1MDAwNWGF0cpjXHUwMDA1k+cmXHUwMDE0MoTCKVxiXCLAdVx1MDAxMTO3Kf+kxyiarc695uztiX6nKTviS27UXG5pXGa4OPX4/CjrX368Z8Uhv9jrXHUwMDEwae/Nibr4sOVuXHUwMDA3aFx1MDAxN9JcZkJcdTAwMWU3QipMandr3cZz7lx1MDAxZSfRsFx1MDAxNUJs8HktoIKw49plYdjZtPdwy27XcmI0MMN1PJ/1T8mQ3G1TgHU6NsVvmlwiNUz9pFx1MDAxY2ln4lx1MDAwM9pBmnZcbtjJqb+Eo1x1MDAwYnuT7fBqtG9De7c3b00/XFyXL1d4L2Hv8GXLePF55/Pf/3dBdyJ9 RBV MatchedSet signal12Match signalSet Completed
  • If wait_for_set_completion = True:
    The function returns at 1 (see diagram below), which occurs when the “set operation” is complete.

  • If wait_for_set_completion = False:
    The function returns at 2, which occurs when the match_signal reaches the match_value.