{ "cells": [ { "cell_type": "markdown", "id": "regular-telling", "metadata": {}, "source": [ "# Group Signals into Devices\n", "\n", "In this tutorial we will group multiple Signals into a simple custom Device,\n", "which enables us to conveniently connect to them and read them in batch.\n", "\n", "## Set up for tutorial\n", "\n", "We'll start our IOCs connected to simulated hardware, some of which implement a [random walk](https://en.wikipedia.org/wiki/Random_walk) that we will use.\n", "\n", "The IOCs may already be running in the background. Run this command to verify\n", "that they are running: it should produce output with STARTING or RUNNING on each line.\n", "In the event of a problem, edit this command to replace `status` with `restart all` and run again." ] }, { "cell_type": "code", "execution_count": null, "id": "guilty-highland", "metadata": {}, "outputs": [], "source": [ "!../supervisor/start_supervisor.sh status" ] }, { "cell_type": "markdown", "id": "present-aurora", "metadata": {}, "source": [ "## Define a Custom Device\n", "\n", "\n", "It's common to have more than one instance of a given piece of hardware and to\n", "present each instance in EPICS with different \"prefixes\" as in:\n", "\n", "```\n", "# Device 1:\n", "random-walk:horiz:dt\n", "random-walk:horiz:x\n", "\n", "# Device 2:\n", "random-walk:vert:dt\n", "random-walk:vert:x\n", "```\n", "\n", "Ophyd makes it easy to take advantage of this nested naming convention of PV names,\n", "where applicable. Define a subclass of :class:`ophyd.Device`:" ] }, { "cell_type": "code", "execution_count": null, "id": "manual-tulsa", "metadata": {}, "outputs": [], "source": [ "from ophyd import Component, Device, EpicsSignal, EpicsSignalRO\n", "\n", "class RandomWalk(Device):\n", " x = Component(EpicsSignalRO, 'x')\n", " dt = Component(EpicsSignal, 'dt')" ] }, { "cell_type": "markdown", "id": "permanent-technology", "metadata": {}, "source": [ "Up to this point we haven't actually created any signals yet or connected\n", "to any hardware. We have only *defined the structure* of this device and\n", "provided the suffixes (``'x'``, ``'dt'``) of the relevant PVs.\n", "\n", "Now, we create an instance of the device, providing the PV prefix that\n", "identifies one of our IOCs." ] }, { "cell_type": "code", "execution_count": null, "id": "better-running", "metadata": {}, "outputs": [], "source": [ "random_walk_horiz = RandomWalk('random-walk:horiz:', name='random_walk_horiz')\n", "random_walk_horiz.wait_for_connection()\n", "random_walk_horiz" ] }, { "cell_type": "markdown", "id": "original-employer", "metadata": {}, "source": [ "It is *conventional* to name the Python variable on the left the same as the\n", "value of ``name``, but not required. That is, this is conventional...\n", "\n", "```\n", "a = RandomWalk(\"...\", name=\"a\")\n", "```\n", "\n", "```\n", "a = RandomWalk(\"...\", name=\"b\") # local variable different from name\n", "a = RandomWalk(\"...\", name=\"some name with spaces in it\")\n", "a = b = RandomWalk(\"...\", name=\"b\") # two local variables\n", "```" ] }, { "cell_type": "markdown", "id": "declared-currency", "metadata": {}, "source": [ "In the same way we can connect to the other IOC. We create a second instance of\n", "the same class." ] }, { "cell_type": "code", "execution_count": null, "id": "electric-acoustic", "metadata": {}, "outputs": [], "source": [ "random_walk_vert = RandomWalk('random-walk:vert:', name='random_walk_vert')\n", "random_walk_vert.wait_for_connection()\n", "random_walk_vert" ] }, { "cell_type": "markdown", "id": "external-happiness", "metadata": {}, "source": [ "## Use it with the Bluesky RunEngine\n", "\n", "The signals can be used by the Bluesky RunEngine. Let's configure a RunEngine\n", "to print a table." ] }, { "cell_type": "code", "execution_count": null, "id": "adjacent-harris", "metadata": {}, "outputs": [], "source": [ "from bluesky import RunEngine\n", "from bluesky.callbacks import LiveTable\n", "RE = RunEngine()\n", "token = RE.subscribe(LiveTable([\"random_walk_horiz_x\", \"random_walk_horiz_dt\"]))" ] }, { "cell_type": "markdown", "id": "editorial-header", "metadata": {}, "source": [ "We can access the components of ``random_walk_horiz`` like ``random_walk_horiz.x``\n", "and use this to read them individually." ] }, { "cell_type": "code", "execution_count": null, "id": "virtual-summer", "metadata": {}, "outputs": [], "source": [ "from bluesky.plans import count\n", "\n", "RE(count([random_walk_horiz.x], num=3, delay=1))" ] }, { "cell_type": "markdown", "id": "indoor-glory", "metadata": {}, "source": [ "We can also read ``random_walk_horiz`` in its entirety as a unit, treating it as\n", "a composite \"detector\"." ] }, { "cell_type": "code", "execution_count": null, "id": "fluid-principle", "metadata": {}, "outputs": [], "source": [ "RE(count([random_walk_horiz], num=3, delay=1))" ] }, { "cell_type": "markdown", "id": "comparable-boost", "metadata": {}, "source": [ "## Assign a \"Kind\" to Components\n", "\n", "In the example just above, notice that we are recording ``random_walk_horiz_dt``\n", "in every row (i.e. every Event) because it is returned alongside\n", "``random_walk_horiz_x`` in the reading." ] }, { "cell_type": "code", "execution_count": null, "id": "least-quarter", "metadata": {}, "outputs": [], "source": [ "random_walk_horiz.read()" ] }, { "cell_type": "markdown", "id": "appointed-kentucky", "metadata": {}, "source": [ "This is probably not necessary. Unless we have some reason to expect that it\n", "could be changed, it would be more useful to record ``random_walk_horiz_dt``\n", "once per Run as part of the device's *configuration*.\n", "\n", "Ophyd enables us to do this like so:" ] }, { "cell_type": "code", "execution_count": null, "id": "announced-round", "metadata": {}, "outputs": [], "source": [ "from ophyd import Kind\n", "\n", "random_walk_horiz.dt.kind = Kind.config" ] }, { "cell_type": "markdown", "id": "interesting-creator", "metadata": {}, "source": [ "As a shorthand, a string alias is also accepted and normalized to enum member of\n", "that name." ] }, { "cell_type": "code", "execution_count": null, "id": "billion-navigator", "metadata": {}, "outputs": [], "source": [ "random_walk_horiz.dt.kind = \"config\"\n", "random_walk_horiz.dt.kind" ] }, { "cell_type": "markdown", "id": "military-inflation", "metadata": {}, "source": [ "Equivalently, we could have set the ``kind`` when we first defined the device, like so:" ] }, { "cell_type": "code", "execution_count": null, "id": "stunning-drama", "metadata": {}, "outputs": [], "source": [ "class RandomWalk(Device):\n", " x = Component(EpicsSignalRO, 'x')\n", " dt = Component(EpicsSignal, 'dt', kind=\"config\")" ] }, { "cell_type": "markdown", "id": "eligible-lawyer", "metadata": {}, "source": [ "Again, either enum ``Kind.config`` or string ``\"config\"`` are accepted.\n", "\n", "The result is that ``random_walk_horiz_dt`` is moved from ``read()`` to\n", "``read_configuration()``:" ] }, { "cell_type": "code", "execution_count": null, "id": "advised-freight", "metadata": {}, "outputs": [], "source": [ "random_walk_horiz.read()" ] }, { "cell_type": "code", "execution_count": null, "id": "informal-venezuela", "metadata": {}, "outputs": [], "source": [ "random_walk_horiz.read_configuration()" ] }, { "cell_type": "markdown", "id": "white-kruger", "metadata": {}, "source": [ "In Bluesky's Document Model, the result of ``device.read()`` is placed in an\n", "Event Document, and the result of ``device.read_configuration()`` is placed in\n", "an Event Descriptor document. The Bluesky RunEngine always calls\n", "``device.read_configuration()`` and captures that information the first time\n", "a given ``device`` is read.\n", "\n", "Other possible values for `Kind` are `omitted`, `normal` and `hinted`. For more details, see the Ophyd documentation for [Signal](https://blueskyproject.io/ophyd/reference/signals.html).\n", "\n", "For a larger example of Kind being used on a real device,\n", "see [the source code for EpicsMotor](https://github.com/bluesky/ophyd/blob/master/ophyd/epics_motor.py)." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.7.10" } }, "nbformat": 4, "nbformat_minor": 5 }