diff --git a/docs/source/examples/de_sscan.ipynb b/docs/source/examples/de_sscan.ipynb
index 14f3af681..fbb312c64 100644
--- a/docs/source/examples/de_sscan.ipynb
+++ b/docs/source/examples/de_sscan.ipynb
@@ -11,10 +11,6 @@
"**Goals**: Demonstrate use of the `sscan` record with Bluesky.\n",
"\n",
"1. Press SCAN button of a preconfigured scan.\n",
- "\n",
- " 1. Use a polling loop to wait for scan to end.\n",
- " 2. Use a Status object to wait for scan to end.\n",
- "\n",
"2. Same example as section 1, but uses\n",
" [SscanRecord](https://bcda-aps.github.io/apstools/latest/api/synApps/_sscan.html)\n",
" instead of\n",
@@ -84,7 +80,11 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "First, connect with the EPICS PV of the button using `ophyd.EpicsSignal`. Once connected, show the current value."
+ "Above, we said the scan can be run by pressing the *SCAN* button in the GUI. Let's do the same thing with bluesky. That is, we'll have bluesky press the *SCAN* button and then wait for the scan to end.\n",
+ "\n",
+ "First, connect with the EPICS PV of the *SCAN* button (`gp:scan1.EXSC`) using\n",
+ "`ophyd.EpicsSignal`. Once the object is connected with the EPICS PV, show the\n",
+ "current value of the PV."
]
},
{
@@ -112,104 +112,46 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "### 1.A. wait with a polling loop\n",
- "\n",
- "Write a bluesky plan that pushes the button, waits a brief moment for the scan\n",
- "to start, then waits for the scan to end. This simple example waits by\n",
- "periodically polling the value of the button."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "metadata": {},
- "outputs": [],
- "source": [
- "def run_sscan():\n",
- " yield from bps.mv(scan_button, 1) # i.e. caput(\"gp:scan1.EXSC\", 1)\n",
- " print(\"Started scan ...\")\n",
- " yield from bps.sleep(0.1) # wait for scan to start\n",
- " while scan_button.get() != 0:\n",
- " yield from bps.sleep(0.1)\n",
- " print(\"Scan done.\")"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Execute the `run_sscan()` plan using the bluesky RunEngine `RE`."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Started scan ...\n",
- "Scan done.\n"
- ]
- },
- {
- "data": {
- "text/plain": [
- "()"
- ]
- },
- "execution_count": 4,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "RE(run_sscan())"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "To get the data from the `sscan` record, we use `ophyd.EpicsSignalRO` to connect with the arrays. We'll do that in a later section of this notebook.\n",
+ "Write a bluesky plan that starts the scan (by pushing the button) and watches\n",
+ "the button's value until it reports the scan ended. As written above, we know\n",
+ "exactly when the scan has ended when the button value changes from `1` to `0`.\n",
"\n",
- "### 1.B. wait with a Status object\n",
- "\n",
- "Let's eliminate that polling loop. This will make the plan respond more quickly\n",
- "when the button changes value. The `ophyd.status.Status` class (runs in the\n",
- "background) reports when the scan has finished or times out. We define an inner\n",
- "`watch_the_button()` function to detect when the button goes from `1` to `0`\n",
- "(the scan stops). We setup a subscription (*after* we start the move) to\n",
- "monitor the button value and respond immediately. \n",
- "\n",
- "We wait for the status object to finish with `st.wait()` where `wait(timeout=None)` blocks until the action completes; when the action has finished successfully, returns `None`; if the action has failed, raises an exception.\n",
+ "\n",
"\n",
- "In a bluesky plan,\n",
- "we'll need to use `apstools.plans.run_blocking_function` with `st.wait()` since\n",
- "it is a blocking function. The statement `yield from\n",
- "run_blocking_function(st.wait)` runs `st.wait()` in a background thread so it\n",
- "does not block the `RE`.\n",
+ "1. The `ophyd.status.Status` class (runs in the background) is used here to\n",
+ " report when the scan has finished, aborted, or has not finished in the\n",
+ " expected time. Here, `st = Status(timeout=20)` creates a status object with a\n",
+ " default timeout of 20s.\n",
"\n",
- "\n",
+ "1. The inner `watch_the_button()` function detects when the button goes from `1`\n",
+ " to `0` (the scan has finished). Once the scan has finished,\n",
+ " `st.set_finished()` sets `st.done=True` and `st.success=True`.\n",
"\n",
- "The `if` statement compares both `old_value` and `value`. We catch the exact\n",
- "event when the scan finishes. This is cautious programming to avoid the odd\n",
- "case when `old_value=0` and `value=0` (occurs when IOC just booted and scan not\n",
- "started yet).\n",
+ " The `if` statement compares both `old_value` and `value`. We catch the exact\n",
+ " event when the scan finishes. This is cautious programming to avoid the odd\n",
+ " case when `old_value=0` and `value=0` (occurs when IOC just booted and scan\n",
+ " not started yet).\n",
"\n",
- "The `ophyd.EpicsSignal` class handles the job of supplying arguments to\n",
- "subscription functions such as `watch_the_button`.\n",
+ "1. A subscription is started (*after* we start the move) to monitor the button\n",
+ " value and respond promptly to any updates of the PV from EPICS. A\n",
+ " subscription starts a CA monitor on the signal and calls `watch_the_button()`\n",
+ " whenever a new value is received. The ophyd `EpicsSignal` class is\n",
+ " responsible for supplying the keyword arguments each time it calls a\n",
+ " subscription function (such as `watch_the_button()`) in response to an EPICS\n",
+ " CA monitor event.\n",
"\n",
- "\n",
+ "1. The plan waits for the scan to finish by waiting for `st.done=True`. We must\n",
+ " use `apstools.plans.run_blocking_function` with `st.wait()` since it is a\n",
+ " blocking function. The statement `run_blocking_function(st.wait)` runs\n",
+ " `st.wait()` in a background thread so it does not block the `RE`.\n",
"\n",
- "Finally, we remove the subscription of the button."
+ "Finally, we remove the subscription of the button.\n",
+ ""
]
},
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
@@ -217,8 +159,9 @@
"from ophyd.status import Status\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",
+ " # Create an instance of the Status class.\n",
+ " # Set a default timeout to 20s.\n",
+ " # If not finished by this time, it raises a timeout exception.\n",
" st = Status(timeout=20)\n",
"\n",
" def watch_the_button(old_value, value, **kwargs):\n",
@@ -232,9 +175,16 @@
" yield from run_blocking_function(st.wait)"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Execute the `run_sscan()` plan using the bluesky RunEngine `RE`."
+ ]
+ },
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": 4,
"metadata": {},
"outputs": [
{
@@ -243,7 +193,7 @@
"()"
]
},
- "execution_count": 6,
+ "execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
@@ -252,20 +202,29 @@
"RE(run_sscan())"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We'll get the data arrays from the `sscan` record in a later section of this notebook."
+ ]
+ },
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. Run preconfigured `sscan` record\n",
"\n",
- "The `apstools.synApps.SscanRecord` class provides access to most of the fields\n",
- "provided by a `sscan` record. Use `SscanRecord` to connect and repeat the above\n",
- "example. With the `SscanRecord` class, the button is called `execute_scan`."
+ "The\n",
+ "[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`."
]
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
@@ -294,7 +253,7 @@
},
{
"cell_type": "code",
- "execution_count": 8,
+ "execution_count": 6,
"metadata": {},
"outputs": [
{
@@ -303,7 +262,7 @@
"()"
]
},
- "execution_count": 8,
+ "execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
@@ -316,17 +275,21 @@
"cell_type": "markdown",
"metadata": {},
"source": [
+ "### Get the data arrays from the `sscan` record\n",
+ "\n",
"Retrieve the data collected by `scan1` as a dictionary of numpy arrays. Include\n",
- "both detectors and the motor. The `sscan` record has buffers capable of\n",
+ "the motor and both detectors. The `sscan` record has buffers capable of\n",
"collecting as many as 1,000 points per array. First get the number of points\n",
"collected, then limit each array length to that number.\n",
"\n",
- "If we write this as a function, we can call it again later. Since it executes quickly, it does not need to be written as a bluesky plan."
+ "Write this as a function so it can be called again later. It does not have any\n",
+ "statements that would block the RunEngine and it executes quickly. It does not\n",
+ "need to be written as a bluesky plan."
]
},
{
"cell_type": "code",
- "execution_count": 9,
+ "execution_count": 7,
"metadata": {},
"outputs": [
{
@@ -335,13 +298,13 @@
"{'m1': array([-1.2 , -1.08, -0.96, -0.84, -0.72, -0.6 , -0.48, -0.36, -0.24,\n",
" -0.12, 0. , 0.12, 0.24, 0.36, 0.48, 0.6 , 0.72, 0.84,\n",
" 0.96, 1.08, 1.2 ]),\n",
- " 'I0': array([1., 1., 1., 1., 2., 1., 2., 1., 2., 3., 5., 3., 3., 2., 1., 1., 2.,\n",
- " 2., 0., 1., 1.], dtype=float32),\n",
- " 'diode': array([1., 1., 1., 1., 1., 0., 1., 1., 3., 3., 4., 4., 3., 2., 2., 1., 1.,\n",
- " 1., 1., 1., 0.], dtype=float32)}"
+ " 'I0': array([1., 1., 1., 1., 1., 1., 1., 1., 2., 3., 3., 4., 4., 2., 1., 1., 1.,\n",
+ " 1., 1., 1., 2.], dtype=float32),\n",
+ " 'diode': array([2., 1., 0., 1., 2., 0., 1., 1., 2., 2., 4., 3., 3., 3., 1., 1., 1.,\n",
+ " 2., 2., 2., 1.], dtype=float32)}"
]
},
- "execution_count": 9,
+ "execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
@@ -372,7 +335,7 @@
},
{
"cell_type": "code",
- "execution_count": 10,
+ "execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
@@ -404,7 +367,7 @@
},
{
"cell_type": "code",
- "execution_count": 11,
+ "execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
@@ -441,7 +404,7 @@
},
{
"cell_type": "code",
- "execution_count": 12,
+ "execution_count": 10,
"metadata": {},
"outputs": [
{
@@ -450,13 +413,13 @@
"()"
]
},
- "execution_count": 12,
+ "execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
- "RE(setup_scan1(-1.1, 1.1, 11, 0.2))"
+ "RE(setup_scan1(-1.2, 1.2, 21, 0.2))"
]
},
{
@@ -468,7 +431,7 @@
},
{
"cell_type": "code",
- "execution_count": 13,
+ "execution_count": 11,
"metadata": {},
"outputs": [
{
@@ -477,7 +440,7 @@
"()"
]
},
- "execution_count": 13,
+ "execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
@@ -496,19 +459,22 @@
},
{
"cell_type": "code",
- "execution_count": 14,
+ "execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
- "{'m1': array([-1.1 , -0.88, -0.66, -0.44, -0.22, 0. , 0.22, 0.44, 0.66,\n",
- " 0.88, 1.1 ]),\n",
- " 'I0': array([0., 1., 2., 1., 1., 3., 3., 2., 1., 1., 0.], dtype=float32),\n",
- " 'diode': array([1., 2., 2., 0., 1., 3., 4., 2., 2., 1., 1.], dtype=float32)}"
+ "{'m1': array([-1.2 , -1.08, -0.96, -0.84, -0.72, -0.6 , -0.48, -0.36, -0.24,\n",
+ " -0.12, 0. , 0.12, 0.24, 0.36, 0.48, 0.6 , 0.72, 0.84,\n",
+ " 0.96, 1.08, 1.2 ]),\n",
+ " 'I0': array([1., 1., 1., 1., 1., 1., 1., 1., 2., 2., 4., 3., 3., 2., 1., 2., 2.,\n",
+ " 1., 1., 1., 1.], dtype=float32),\n",
+ " 'diode': array([1., 1., 2., 1., 2., 1., 0., 1., 2., 2., 4., 3., 3., 1., 1., 2., 0.,\n",
+ " 1., 1., 1., 1.], dtype=float32)}"
]
},
- "execution_count": 14,
+ "execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
@@ -546,7 +512,7 @@
},
{
"cell_type": "code",
- "execution_count": 15,
+ "execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
@@ -582,7 +548,7 @@
},
{
"cell_type": "code",
- "execution_count": 16,
+ "execution_count": 14,
"metadata": {},
"outputs": [
{
@@ -591,7 +557,7 @@
"0"
]
},
- "execution_count": 16,
+ "execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
@@ -615,16 +581,16 @@
},
{
"cell_type": "code",
- "execution_count": 17,
+ "execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
- "('eaa4598f-2e97-4e19-9e7c-4818ff62e042',)"
+ "('b40e4926-d365-4c72-9936-76d28425c7fc',)"
]
},
- "execution_count": 17,
+ "execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
@@ -643,21 +609,21 @@
},
{
"cell_type": "code",
- "execution_count": 18,
+ "execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"BlueskyRun\n",
- " uid='eaa4598f-2e97-4e19-9e7c-4818ff62e042'\n",
+ " uid='b40e4926-d365-4c72-9936-76d28425c7fc'\n",
" exit_status='success'\n",
- " 2024-03-18 14:34:24.557 -- 2024-03-18 14:34:24.563\n",
+ " 2024-03-18 16:06:34.111 -- 2024-03-18 16:06:34.117\n",
" Streams:\n",
" * primary\n"
]
},
- "execution_count": 18,
+ "execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
@@ -677,7 +643,7 @@
},
{
"cell_type": "code",
- "execution_count": 19,
+ "execution_count": 17,
"metadata": {},
"outputs": [
{
@@ -1047,29 +1013,32 @@
" fill: currentColor;\n",
"}\n",
"