Skip to content

Commit 7baaad3

Browse files
authored
Merge pull request #14 from dls-controls/documentation_update
Docs re-write, including for asyncio support
2 parents 3b3c47b + 79e0cba commit 7baaad3

File tree

15 files changed

+240
-189
lines changed

15 files changed

+240
-189
lines changed

README.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pythonIoc
66

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

1212
============== ==============================================================
@@ -15,7 +15,7 @@ Source code https://github.com/dls-controls/pythonIoc
1515
Documentation https://dls-controls.github.io/pythonIoc
1616
============== ==============================================================
1717

18-
A simple example of the use of this library is the following:
18+
A simple example of the use of this library:
1919

2020
.. code:: python
2121
@@ -40,12 +40,12 @@ A simple example of the use of this library is the following:
4040
ai.set(ai.get() + 1)
4141
cothread.Sleep(1)
4242
43+
4344
cothread.Spawn(update)
4445
4546
# Finally leave the IOC running with an interactive shell.
4647
softioc.interactive_ioc(globals())
4748
48-
4949
.. |code_ci| image:: https://github.com/dls-controls/pythonIoc/workflows/Code%20CI/badge.svg?branch=master
5050
:target: https://github.com/dls-controls/pythonIoc/actions?query=workflow%3A%22Code+CI%22
5151
:alt: Code CI

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
intersphinx_mapping = dict(
9797
python=('https://docs.python.org/3/', None),
9898
cothread=("https://cothread.readthedocs.org/en/stable/", None),
99+
aioca=("https://dls-controls.github.io/aioca/master/", None),
99100
epicsdbbuilder=(
100101
"https://dls-controls.github.io/epicsdbbuilder/master/", None)
101102
)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Import the basic framework components.
2+
from softioc import softioc, builder, asyncio_dispatcher
3+
import asyncio
4+
5+
# Create an asyncio dispatcher, the event loop is now running
6+
dispatcher = asyncio_dispatcher.AsyncioDispatcher()
7+
8+
# Set the record prefix
9+
builder.SetDeviceName("MY-DEVICE-PREFIX")
10+
11+
# Create some records
12+
ai = builder.aIn('AI', initial_value=5)
13+
ao = builder.aOut('AO', initial_value=12.45, always_update=True,
14+
on_update=lambda v: ai.set(v))
15+
16+
# Boilerplate get the IOC started
17+
builder.LoadDatabase()
18+
softioc.iocInit(dispatcher)
19+
20+
# Start processes required to be run after iocInit
21+
async def update():
22+
while True:
23+
ai.set(ai.get() + 1)
24+
await asyncio.sleep(1)
25+
26+
asyncio.run_coroutine_threadsafe(update(), dispatcher.loop)
27+
28+
# Finally leave the IOC running with an interactive shell.
29+
softioc.interactive_ioc(globals())
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Import the basic framework components.
2+
from softioc import softioc, builder
3+
import cothread
4+
5+
# Set the record prefix
6+
builder.SetDeviceName("MY-DEVICE-PREFIX")
7+
8+
# Create some records
9+
ai = builder.aIn('AI', initial_value=5)
10+
ao = builder.aOut('AO', initial_value=12.45, always_update=True,
11+
on_update=lambda v: ai.set(v))
12+
13+
# Boilerplate get the IOC started
14+
builder.LoadDatabase()
15+
softioc.iocInit()
16+
17+
# Start processes required to be run after iocInit
18+
def update():
19+
while True:
20+
ai.set(ai.get() + 1)
21+
cothread.Sleep(1)
22+
23+
cothread.Spawn(update)
24+
25+
# Finally leave the IOC running with an interactive shell.
26+
softioc.interactive_ioc(globals())
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from softioc import softioc
2+
from cothread.catools import caget, caput, camonitor
3+
4+
print(caget("MY-DEVICE-PREFIX:AI"))
5+
print(caget("MY-DEVICE-PREFIX:AO"))
6+
print(caput("MY-DEVICE-PREFIX:AO", "999"))
7+
print(caget("MY-DEVICE-PREFIX:AO"))
8+
9+
softioc.interactive_ioc(globals())

docs/explanations/why-use-pythonIoc.rst

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ allows you to write this as:
3838
# Leave the IOC running with an interactive shell.
3939
softioc.interactive_ioc(globals())
4040
41-
ADD THE CONCENTRATOR USE CASE HERE
42-
4341
Dynamically created PVs
4442
-----------------------
4543

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
Create a Publishable IOC
2+
========================
3+
4+
As seen in `../tutorials/creating-an-ioc`, a single Python script can be an IOC.
5+
It is also possible (and the most common situation) to have an entire Python module
6+
comprising an IOC. This guide explains both, as well as how to publish an IOC within
7+
the DLS environment.
8+
9+
Single File IOC
10+
----------------
11+
An IOC that is entirely contained within a single Python source file can be used as an
12+
IOC inside DLS simply by adding this shebang line::
13+
14+
#!/dls_sw/prod/python3/RHEL7-x86_64/softioc/3.0b2/prefix/bin/pythonIoc
15+
16+
17+
IOC entry point for a module
18+
------------------------------
19+
If your IOC is more complicated than one file, it is recommended to write a python
20+
module (including docs/tests/etc.). The Panda Blocks Client will be an example of
21+
this.
22+
23+
24+
Make an IOC publishable at DLS
25+
------------------------------
26+
To make the IOC publishable, a makefile is required:
27+
28+
``Makefile``
29+
This file is necessary in order to run ``dls-release.py``, and needs to have
30+
both ``install`` and ``clean`` targets, but doesn't need to actually do
31+
anything. Thus the following content for this file is enough::
32+
33+
install:
34+
clean:
35+

docs/how-to/read-data-from-ioc.rst

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
Read data from an IOC
2+
======================
3+
4+
This guide explains how to read data from an IOC in a separate Python program.
5+
6+
.. note::
7+
Please ensure your firewall allows both TCP and UDP traffic on ports 5064 and 5065.
8+
These are used by EPICS for channel access to the PVs.
9+
10+
11+
To start, run the `cothread` IOC from `../tutorials/creating-an-ioc` or the
12+
`asyncio` IOC from `use-asyncio-in-an-ioc` and leave it running at the
13+
interactive shell.
14+
15+
We will read data from that IOC using this script:
16+
17+
.. literalinclude:: ../examples/example_read_from_ioc.py
18+
19+
.. note::
20+
You may see warnings regarding the missing "caRepeater" program. This is an EPICS tool
21+
that is used to track when PVs start and stop. It is not required for this simple example,
22+
and so the warning can be ignored.
23+
24+
From the interactive command line you can now use the ``caget`` and ``caput`` functions to operate on
25+
the PVs exposed in the IOC. Another interesting command to try is::
26+
27+
camonitor("MY-DEVICE-PREFIX:AI", lambda val: print(val))
28+
29+
30+
You should observe the value of ``AI`` being printed out, once per second, every time the PV value
31+
updates.
Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,33 @@
11
Use `asyncio` in an IOC
22
=======================
33

4-
Write about the differences creating an IOC using `AsyncioDispatcher`
4+
There are two libraries available for asynchronous operations in PythonIOC:
5+
`cothread` and `asyncio`. This guide shows how to use the latter in
6+
an IOC.
7+
8+
.. note::
9+
This page only explains the differences between using `cothread` and `asyncio`.
10+
For more thorough explanation of the IOC itself see `../tutorials/creating-an-ioc`
11+
12+
.. literalinclude:: ../examples/example_asyncio_ioc.py
13+
14+
15+
The ``dispatcher`` is created and passed to :func:`~softioc.softioc.iocInit`. This is what
16+
allows the use of `asyncio` functions in this IOC. It contains a new event loop to handle
17+
this.
18+
19+
The ``async update`` function will increment the value of ``ai`` once per second,
20+
sleeping that coroutine between updates.
21+
Note that we run this coroutine in the ``loop`` of the ``dispatcher``, and not in the
22+
main event loop.
23+
24+
This IOC will, like the one in `../tutorials/creating-an-ioc`, leave an interactive
25+
shell open. The values of the PVs can be queried using the methods defined in the
26+
`softioc.softioc` module.
27+
28+
29+
Asynchronous Channel Access
30+
---------------------------
31+
32+
PVs can be retrieved externally from a PV in an asynchronous manner by using the :py`aioca` module.
33+
It provides ``await``-able implementations of ``caget``, ``caput``, etc. See that module for more information.

docs/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ Table Of Contents
5959
:maxdepth: 1
6060

6161
how-to/use-asyncio-in-an-ioc
62+
how-to/make-publishable-ioc
63+
how-to/read-data-from-ioc
6264

6365
.. toctree::
6466
:caption: Explanations

0 commit comments

Comments
 (0)