Skip to content

Commit

Permalink
Merge pull request #14 from dls-controls/documentation_update
Browse files Browse the repository at this point in the history
Docs re-write, including for asyncio support
  • Loading branch information
coretl authored Jul 2, 2021
2 parents 3b3c47b + 79e0cba commit 7baaad3
Show file tree
Hide file tree
Showing 15 changed files with 240 additions and 189 deletions.
6 changes: 3 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pythonIoc

This module allows an EPICS IOC with Python Device Support to be run from within
the Python interpreter. Records can be programmatically created and arbitrary
Python code run to updated them and respond to caputs. It supports cothread and
Python code run to update them and respond to caputs. It supports cothread and
asyncio for concurrency.

============== ==============================================================
Expand All @@ -15,7 +15,7 @@ Source code https://github.com/dls-controls/pythonIoc
Documentation https://dls-controls.github.io/pythonIoc
============== ==============================================================

A simple example of the use of this library is the following:
A simple example of the use of this library:

.. code:: python
Expand All @@ -40,12 +40,12 @@ A simple example of the use of this library is the following:
ai.set(ai.get() + 1)
cothread.Sleep(1)
cothread.Spawn(update)
# Finally leave the IOC running with an interactive shell.
softioc.interactive_ioc(globals())
.. |code_ci| image:: https://github.com/dls-controls/pythonIoc/workflows/Code%20CI/badge.svg?branch=master
:target: https://github.com/dls-controls/pythonIoc/actions?query=workflow%3A%22Code+CI%22
:alt: Code CI
Expand Down
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
intersphinx_mapping = dict(
python=('https://docs.python.org/3/', None),
cothread=("https://cothread.readthedocs.org/en/stable/", None),
aioca=("https://dls-controls.github.io/aioca/master/", None),
epicsdbbuilder=(
"https://dls-controls.github.io/epicsdbbuilder/master/", None)
)
Expand Down
29 changes: 29 additions & 0 deletions docs/examples/example_asyncio_ioc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Import the basic framework components.
from softioc import softioc, builder, asyncio_dispatcher
import asyncio

# Create an asyncio dispatcher, the event loop is now running
dispatcher = asyncio_dispatcher.AsyncioDispatcher()

# Set the record prefix
builder.SetDeviceName("MY-DEVICE-PREFIX")

# Create some records
ai = builder.aIn('AI', initial_value=5)
ao = builder.aOut('AO', initial_value=12.45, always_update=True,
on_update=lambda v: ai.set(v))

# Boilerplate get the IOC started
builder.LoadDatabase()
softioc.iocInit(dispatcher)

# Start processes required to be run after iocInit
async def update():
while True:
ai.set(ai.get() + 1)
await asyncio.sleep(1)

asyncio.run_coroutine_threadsafe(update(), dispatcher.loop)

# Finally leave the IOC running with an interactive shell.
softioc.interactive_ioc(globals())
26 changes: 26 additions & 0 deletions docs/examples/example_cothread_ioc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Import the basic framework components.
from softioc import softioc, builder
import cothread

# Set the record prefix
builder.SetDeviceName("MY-DEVICE-PREFIX")

# Create some records
ai = builder.aIn('AI', initial_value=5)
ao = builder.aOut('AO', initial_value=12.45, always_update=True,
on_update=lambda v: ai.set(v))

# Boilerplate get the IOC started
builder.LoadDatabase()
softioc.iocInit()

# Start processes required to be run after iocInit
def update():
while True:
ai.set(ai.get() + 1)
cothread.Sleep(1)

cothread.Spawn(update)

# Finally leave the IOC running with an interactive shell.
softioc.interactive_ioc(globals())
9 changes: 9 additions & 0 deletions docs/examples/example_read_from_ioc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from softioc import softioc
from cothread.catools import caget, caput, camonitor

print(caget("MY-DEVICE-PREFIX:AI"))
print(caget("MY-DEVICE-PREFIX:AO"))
print(caput("MY-DEVICE-PREFIX:AO", "999"))
print(caget("MY-DEVICE-PREFIX:AO"))

softioc.interactive_ioc(globals())
2 changes: 0 additions & 2 deletions docs/explanations/why-use-pythonIoc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ allows you to write this as:
# Leave the IOC running with an interactive shell.
softioc.interactive_ioc(globals())
ADD THE CONCENTRATOR USE CASE HERE

Dynamically created PVs
-----------------------

Expand Down
35 changes: 35 additions & 0 deletions docs/how-to/make-publishable-ioc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Create a Publishable IOC
========================

As seen in `../tutorials/creating-an-ioc`, a single Python script can be an IOC.
It is also possible (and the most common situation) to have an entire Python module
comprising an IOC. This guide explains both, as well as how to publish an IOC within
the DLS environment.

Single File IOC
----------------
An IOC that is entirely contained within a single Python source file can be used as an
IOC inside DLS simply by adding this shebang line::

#!/dls_sw/prod/python3/RHEL7-x86_64/softioc/3.0b2/prefix/bin/pythonIoc


IOC entry point for a module
------------------------------
If your IOC is more complicated than one file, it is recommended to write a python
module (including docs/tests/etc.). The Panda Blocks Client will be an example of
this.


Make an IOC publishable at DLS
------------------------------
To make the IOC publishable, a makefile is required:

``Makefile``
This file is necessary in order to run ``dls-release.py``, and needs to have
both ``install`` and ``clean`` targets, but doesn't need to actually do
anything. Thus the following content for this file is enough::

install:
clean:

31 changes: 31 additions & 0 deletions docs/how-to/read-data-from-ioc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Read data from an IOC
======================

This guide explains how to read data from an IOC in a separate Python program.

.. note::
Please ensure your firewall allows both TCP and UDP traffic on ports 5064 and 5065.
These are used by EPICS for channel access to the PVs.


To start, run the `cothread` IOC from `../tutorials/creating-an-ioc` or the
`asyncio` IOC from `use-asyncio-in-an-ioc` and leave it running at the
interactive shell.

We will read data from that IOC using this script:

.. literalinclude:: ../examples/example_read_from_ioc.py

.. note::
You may see warnings regarding the missing "caRepeater" program. This is an EPICS tool
that is used to track when PVs start and stop. It is not required for this simple example,
and so the warning can be ignored.

From the interactive command line you can now use the ``caget`` and ``caput`` functions to operate on
the PVs exposed in the IOC. Another interesting command to try is::

camonitor("MY-DEVICE-PREFIX:AI", lambda val: print(val))


You should observe the value of ``AI`` being printed out, once per second, every time the PV value
updates.
31 changes: 30 additions & 1 deletion docs/how-to/use-asyncio-in-an-ioc.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,33 @@
Use `asyncio` in an IOC
=======================

Write about the differences creating an IOC using `AsyncioDispatcher`
There are two libraries available for asynchronous operations in PythonIOC:
`cothread` and `asyncio`. This guide shows how to use the latter in
an IOC.

.. note::
This page only explains the differences between using `cothread` and `asyncio`.
For more thorough explanation of the IOC itself see `../tutorials/creating-an-ioc`

.. literalinclude:: ../examples/example_asyncio_ioc.py


The ``dispatcher`` is created and passed to :func:`~softioc.softioc.iocInit`. This is what
allows the use of `asyncio` functions in this IOC. It contains a new event loop to handle
this.

The ``async update`` function will increment the value of ``ai`` once per second,
sleeping that coroutine between updates.
Note that we run this coroutine in the ``loop`` of the ``dispatcher``, and not in the
main event loop.

This IOC will, like the one in `../tutorials/creating-an-ioc`, leave an interactive
shell open. The values of the PVs can be queried using the methods defined in the
`softioc.softioc` module.


Asynchronous Channel Access
---------------------------

PVs can be retrieved externally from a PV in an asynchronous manner by using the :py`aioca` module.
It provides ``await``-able implementations of ``caget``, ``caput``, etc. See that module for more information.
2 changes: 2 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ Table Of Contents
:maxdepth: 1

how-to/use-asyncio-in-an-ioc
how-to/make-publishable-ioc
how-to/read-data-from-ioc

.. toctree::
:caption: Explanations
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ starting the IOC.
This must be called exactly once after creating all the records required by
the IOC and before calling :func:`~softioc.softioc.iocInit`. After this
function has been called none of the functions provided by
:mod:`softioc.builder` are usable.
`softioc.builder` are usable.

.. automodule:: softioc.alarm

Expand Down
Loading

0 comments on commit 7baaad3

Please sign in to comment.