{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Trigger a Device with the EPICS record .PROC field\n", "\n", "The EPICS `.PROC` field is one of the fields\n", "[common](https://epics-base.github.io/epics-base/dbCommonRecord.html) to all\n", "EPICS records. It tells the IOC's database to execute the standard actions for\n", "that record's data. When the record is in `Passive` [scan\n", "mode](https://docs.epics-controls.org/en/latest/process-database/EPICS_Process_Database_Concepts.html#scanning-specification),\n", "sending a `1` value to the `.PROC` field *processes* the record.\n", "\n", "EPICS record processing is similar to the ophyd `trigger()`\n", "[method](https://github.com/bluesky/ophyd/blob/5c03c3fff974dc6390836fc83dae4c247a35e662/ophyd/device.py#L1438-L1439)\n", "which tells the [Device](https://github.com/bluesky/ophyd/blob/5c03c3fff974dc6390836fc83dae4c247a35e662/ophyd/device.py#L780-L784) to run its data acquisition method." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from ophyd import Component, Device, EpicsSignal" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's make a custom ophyd `Device`, limiting our attention only to the fields of interest for this howto guide.\n", "\n", "field | description\n", "---- | ----\n", "`.A` | The *a* value.\n", "`.CLCA` | The calculation to produce a new *A* value when the record is processed.\n", "`.PROC` | Send a `1` to this field to process the record.\n", "`.SCAN` | The record's scan mode selection.\n", "\n", "Here, we add the `trigger_value` [keyword argument](https://github.com/bluesky/ophyd/blob/master/ophyd/device.py#L139-L141). **This** is how we send a `1` to the `.PROC` field when the Device is triggered.\n", "\n", "It is worth commenting about the `kind` keyword value. The [kind](https://blueskyproject.io/ophyd/user/reference/signals.html#kind) attribute is the means to identify a signal that is relevant for handling by a callback.\n", "\n", "kind | usage\n", "---- | ----\n", "hinted | attribute should be reported by `.read()`\n", "config | value is metadata, should be reported by `.read_configuration()`\n", "omitted | value is not reportable" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "class Transform(Device):\n", " a = Component(EpicsSignal, \".A\", kind=\"hinted\")\n", " calc_a = Component(EpicsSignal, \".CLCA\", kind=\"config\", string=True)\n", " process_record = Component(EpicsSignal, \".PROC\", kind=\"omitted\", trigger_value=1)\n", " scan_mode = Component(EpicsSignal, \".SCAN\", kind=\"config\", string=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Create an ophyd object and connect it with EPICS." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "xform = Transform(\"gp:userTran11\", name=\"xform\")\n", "xform.wait_for_connection()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Print the summary ophyd of the Device." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "data keys (* hints)\n", "-------------------\n", "*xform_a\n", "\n", "read attrs\n", "----------\n", "a EpicsSignal ('xform_a')\n", "\n", "config keys\n", "-----------\n", "xform_calc_a\n", "xform_scan_mode\n", "\n", "configuration attrs\n", "-------------------\n", "calc_a EpicsSignal ('xform_calc_a')\n", "scan_mode EpicsSignal ('xform_scan_mode')\n", "\n", "unused attrs\n", "------------\n", "process_record EpicsSignal ('xform_process_record')\n", "\n" ] } ], "source": [ "xform.summary()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Show the values as they exist now using the `.read()` method." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "xform.read()=OrderedDict([('xform_a', {'value': 2.0, 'timestamp': 1714336346.373808})])\n" ] } ], "source": [ "print(f\"{xform.read()=}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Tell ophyd to trigger the Device (which tells the transform record to process) using the `.trigger()` method.\n", "\n", "Note that only the *a* value is reported. All other attributes are either\n", "configuration or not reportable (such as `process_record`)." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "xform.read()=OrderedDict([('xform_a', {'value': 3.0, 'timestamp': 1714337169.963434})])\n" ] } ], "source": [ "xform.trigger()\n", "print(f\"{xform.read()=}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Reset the Device before we demonstrate." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "xform.read()=OrderedDict([('xform_a', {'value': 0.0, 'timestamp': 1714337169.970021})])\n" ] } ], "source": [ "xform.calc_a.put(\"\")\n", "xform.a.put(0)\n", "xform.scan_mode.put(\"Passive\")\n", "print(f\"{xform.read()=}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Define the calculation to run (increment *a*) each time the record is processed." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "xform.calc_a.put(\"A+1\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Print the value before triggering (processing), trigger the, then print the\n", "value afterwards. We expect the value will increment by `1`." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "xform.read()=OrderedDict([('xform_a', {'value': 0.0, 'timestamp': 1714337169.970021})])\n", "xform.read()=OrderedDict([('xform_a', {'value': 1.0, 'timestamp': 1714337169.982637})])\n" ] } ], "source": [ "print(f\"{xform.read()=}\")\n", "xform.trigger()\n", "print(f\"{xform.read()=}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's do the same thing in a bluesky plan. First, we need some parts from the bluesky package. These are the most basic parts:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "from bluesky import RunEngine\n", "from bluesky import plan_stubs as bps\n", "\n", "RE = RunEngine()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Define the bluesky plan. Instead of calling the Device's `.trigger()` method\n", "directly, we let the RunEngine\n", "[handle](https://github.com/bluesky/bluesky/blob/main/src/bluesky/plan_stubs.py#L447-L449)\n", "that task (with `bps.trigger()`), in case the device takes some time to\n", "complete its trigger action(s)." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "def plan():\n", " print(f\"{xform.read()=}\")\n", " yield from bps.trigger(xform)\n", " print(f\"{xform.read()=}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Run the plan." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "xform.read()=OrderedDict([('xform_a', {'value': 1.0, 'timestamp': 1714337169.982637})])\n", "xform.read()=OrderedDict([('xform_a', {'value': 2.0, 'timestamp': 1714337170.147542})])\n" ] }, { "data": { "text/plain": [ "()" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "RE(plan())" ] } ], "metadata": { "kernelspec": { "display_name": "bluesky_2024_2", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.8" } }, "nbformat": 4, "nbformat_minor": 2 }