From 5e73032cec7929fbb0fa21ce639e6df53977b1ad Mon Sep 17 00:00:00 2001 From: Pete R Jemian Date: Wed, 20 Mar 2024 13:52:07 -0500 Subject: [PATCH] DOC #940 --- docs/source/examples/de_sscan.ipynb | 56 ++++++++++++++--------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/docs/source/examples/de_sscan.ipynb b/docs/source/examples/de_sscan.ipynb index 96fe68c0f..2a6f8723a 100644 --- a/docs/source/examples/de_sscan.ipynb +++ b/docs/source/examples/de_sscan.ipynb @@ -144,15 +144,16 @@ "\n", "Polling loops are discouraged because:\n", "\n", - "- they are not efficient (involving waiting periods of arbitrary duration)\n", - "- they do not handle timeouts or settling times\n", - "- they do not handle other Python exceptions\n", - "- they introduce additional loops into the existing `RE` main loop\n", + "- they are not efficient (involving waiting periods of empirical duration)\n", + "- they do not handle timeouts, settling times, Python exceptions\n", + "- the `RE` already has a main event loop\n", + "- we often want to watch *multiple* \n", + " [signals](https://blueskyproject.io/ophyd/user/tutorials/single-PV.html#set)\n", + " with different update rates or complex logic\n", "\n", - "Instead of polling the value of an EpicsSignal, it is more efficient to set up\n", - "an EPICS CA monitor on the EpicsSignal.\n", - "When new values of the signal are reported by EPICS, a designated function is\n", - "called to respond.\n", + "Instead of polling the value of an EpicsSignal, it is more efficient to start an\n", + "EPICS CA monitor on the EpicsSignal. When new values of the signal are reported\n", + "by EPICS as CA monitor events, a designated *callback* function is called to respond.\n", "\n", "Ophyd\n", "[Status](https://blueskyproject.io/ophyd/ser/generated/ophyd.status.Status.html#ophyd.status.Status)\n", @@ -163,23 +164,23 @@ "See the ophyd\n", "[tutorial](https://blueskyproject.io/tutorials/Ophyd/02%20-%20Complex%20Behaviors%20%28Set%20and%20Multiple%20PVs%29.html#adding-a-set-method-to-device)\n", "for use of a status object with a `.set()` method (which is the method called by\n", - "`bps.mv()`). It is not intuitive to use something like `bps.mv(scan_button, 1)`\n", + "`bps.mv()`). It is not intuitive to use `bps.mv(scan_button, 1)`\n", "here. That would only trigger the scan to *start* but would not wait for the\n", "scan button value to return to `0`. We also want to wait until the scan is\n", "complete.\n", "\n", - "There is a different bluesky plan stub to use in this case: `bps.trigger()`.\n", - "This triggers an ophyd object (by calling the object's `.trigger()` method) and\n", - "(optionally) waits for that trigger to report it is done. It waits using the\n", - "ophyd Status object returned by the object's `.trigger()` method.\n", + "Instead, `bps.trigger(ophyd_object)` tells the ophyd object (such as a scaler or\n", + "area detector) to acquire its data. This triggers the ophyd object (by calling\n", + "the object's `.trigger()` method which returns an ophyd Status object) and\n", + "(optionally) waits for that status object to report it is done.\n", "\n", "We can add such a `.trigger()` method if we create a subclass of `EpicsSignal`.\n", "The `.trigger()` method is called from a bluesky plan using\n", "`bps.trigger(scan_button)`.\n", "\n", - "We use the\n", + "In our `.trigger()` method, our status object is built from the\n", "[SubscriptionStatus](https://blueskyproject.io/ophyd/user/generated/ophyd.status.SubscriptionStatus.html#ophyd-status-subscriptionstatus)\n", - "class which manages the subscription to CA monitor events. The designated\n", + "class, which manages the subscription to CA monitor events. The designated\n", "function receives `old_value` and `value` from a CA monitor event and returns a\n", "boolean value. Once the scan ends, the status object is set to `done=True` and\n", "`success=True` and the CA monitor subscription is removed.\n", @@ -269,7 +270,9 @@ "[SscanRecord](https://bcda-aps.github.io/apstools/latest/api/synApps/_sscan.html#apstools.synApps.sscan.SscanRecord)\n", "class from `apstools.synApps` provides access to most fields of the `sscan`\n", "record. Use `SscanRecord` to connect with `gp:scan1`. Repeat the above example.\n", - "In the `SscanRecord` class, the scan button is called `execute_scan`." + "In the `SscanRecord` class, the scan button is called `execute_scan`.\n", + "\n", + "*Again, it is recommended to use an ophyd Status object instead of a polling loop.*" ] }, { @@ -284,21 +287,12 @@ "scan1.wait_for_connection()\n", "\n", "def run_sscan():\n", - " # Create an instance of the Status object.\n", - " # If it is not marked as finished within 20s, it will raise a timeout exception.\n", - " st = Status(timeout=20)\n", - "\n", - " def watch_execute_scan(old_value, value, **kwargs):\n", - " # Watch for scan1.EXSC to change from 1 to 0 (when the scan ends).\n", - " if old_value == 1 and value == 0:\n", - " # mark as finished (successfully).\n", - " st.set_finished()\n", - " # Remove the subscription.\n", - " scan1.execute_scan.clear_sub(watch_execute_scan)\n", - "\n", " yield from bps.mv(scan1.execute_scan, 1)\n", - " scan1.execute_scan.subscribe(watch_execute_scan)\n", - " yield from run_blocking_function(st.wait)" + "\n", + " # Again, it is advised to use a Status object instead of a polling loop.\n", + " # Wait for the scan to end with a polling loop.\n", + " while scan1.execute_scan.get() != 0:\n", + " yield from bps.sleep(0.1)" ] }, { @@ -422,6 +416,8 @@ "metadata": {}, "outputs": [], "source": [ + "from apstools.plans import run_blocking_function\n", + "\n", "def setup_scan1(start, finish, npts, ct=1):\n", " yield from bps.mv(scaler1.preset_time, ct) # counting time/point\n", " yield from run_blocking_function(scan1.reset)\n",