From 6d18b9a1a13ceca46d7a3ea3d0c9fbc764d696bd Mon Sep 17 00:00:00 2001 From: AlexWells Date: Fri, 25 Jun 2021 08:23:37 +0100 Subject: [PATCH 01/14] fix typo --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index b2e808e5..e5bbb862 100644 --- a/README.rst +++ b/README.rst @@ -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. ============== ============================================================== From f198e739f10cc7fb2e8b68bfa24b4350d7c6aed1 Mon Sep 17 00:00:00 2001 From: AlexWells Date: Fri, 25 Jun 2021 16:30:40 +0100 Subject: [PATCH 02/14] Beginning to rewrite create-an-ioc. Extracted out the example file for ease of code sharing. Need to extract "creating a publishable ioc" into a how-to guide shortly. --- README.rst | 28 +---- docs/examples/example_cothread_ioc.py | 26 +++++ docs/tutorials/creating-an-ioc.rst | 160 ++++++++++---------------- 3 files changed, 86 insertions(+), 128 deletions(-) create mode 100644 docs/examples/example_cothread_ioc.py diff --git a/README.rst b/README.rst index e5bbb862..ccb33325 100644 --- a/README.rst +++ b/README.rst @@ -17,33 +17,7 @@ Documentation https://dls-controls.github.io/pythonIoc A simple example of the use of this library is the following: -.. code:: python - - # 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, 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()) +.. literalinclude:: examples/example_cothread_ioc.py .. |code_ci| image:: https://github.com/dls-controls/pythonIoc/workflows/Code%20CI/badge.svg?branch=master diff --git a/docs/examples/example_cothread_ioc.py b/docs/examples/example_cothread_ioc.py new file mode 100644 index 00000000..004035e8 --- /dev/null +++ b/docs/examples/example_cothread_ioc.py @@ -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, 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()) diff --git a/docs/tutorials/creating-an-ioc.rst b/docs/tutorials/creating-an-ioc.rst index 4de37e3d..7c44388e 100644 --- a/docs/tutorials/creating-an-ioc.rst +++ b/docs/tutorials/creating-an-ioc.rst @@ -1,126 +1,79 @@ Creating an IOC =============== -THIS NEEDS UPDATING - -Using ``pythonSoftIoc`` ------------------------ - -Probably the best way to use ``pythonSoftIoc`` is to start by copying fragments -of a simple example such as ``CS-DI-IOC-02``. This consists of the following -elements: - -1. A startup shell script ``start-ioc`` which launches the soft IOC using a - production build of ``pythonSoftIoc``. This script typically looks like - this:: - - #!/bin/sh - - PYIOC=/path/to/pythonSoftIoc/pythonIoc - - cd "$(dirname "$0")" - exec $PYIOC start_ioc.py "$@" - -2. The startup Python script. This establishes the essential component - versions (apart from the ``pythonSoftIoc`` version), performs the appropriate - initialisation and starts the IOC running. The following template is a - useful starting point:: - - from pkg_resources import require - require('cothread==2.12') - require('epicsdbbuilder==1.0') - - # Import the basic framework components. - from softioc import softioc, builder - import cothread - - # Import any modules required to run the IOC - import ... - - # Boilerplate get the IOC started - builder.LoadDatabase() - softioc.iocInit() - - # Start processes required to be run after iocInit - ... - - # Finally leave the IOC running with an interactive shell. - softioc.interactive_ioc(globals()) +Introduction +------------ - Note that the use of ``require`` is specific to DLS, and you may have a - different way of managing your installations. +Once the module has been installed (see :doc:`installation`) we can create a +simple EPICS Input/Output Controller (IOC). -.. _numpy: http://www.numpy.org/ -.. _cothread: https://github.com/dls-controls/cothread -.. _epicsdbbuilder: https://github.com/Araneidae/epicsdbbuilder +An EPICS IOC created with the help of ``pythonIoc`` and :mod:`softioc` is +referred to as a "Python soft IOC". The code below illustrates a simple IOC +with two Process Variables (PVs): +.. literalinclude:: ../examples/example_cothread_ioc.py +This example script illustrates the following points. -Introduction ------------- +.. literalinclude:: ../examples/example_cothread_ioc.py + :start-after: # Import + :end-before: # Set -The Python Soft IOC consists of two components: a command ``pythonIoc`` and an -associated library :mod:`softioc`. The ``pythonIoc`` command consists of a bare -EPICS IOC linked together with the DLS Python interpreter and configured so that -startup arguments are interpreted by the Python interpreter -- this means that -when ``pythonIoc`` is run it behaves the same as running ``dls-python``. +The :mod:`softioc` library is part of ``pythonIoc``. The two submodules +:mod:`softioc.softioc` and :mod:`softioc.builder` provide the basic +functionality for Python soft IOCs and are the ones that are normally used. -Scripts run from within ``pythonIoc`` differ from standard Python scripts in one -detail: they have the ability to create and publish PVs through the -:mod:`softioc` library. Typically both :mod:`cothread` and -:mod:`epicsdbbuilder` will be recruited to help: :mod:`cothread` is used for -dispatching OUT record processing callback methods, :mod:`epicsdbbuilder` is -used for constructing records during IOC initialisation. -An EPICS IOC created with the help of ``pythonIoc`` and :mod:`softioc` is -referred to as a "Python soft IOC". The code below illustrates a simple IOC -with one PV:: +.. literalinclude:: ../examples/example_cothread_ioc.py + :start-after: # Create + :end-before: # Boilerplate - # DLS requires - from pkg_resources import require - require('cothread==2.12') - require('epicsdbbuilder==1.0') +PVs are normally created dynamically using :mod:`softioc.builder`. All PV +creation must be done before initialising the IOC. We define `on_update` for +``ao`` such that whenever we set ``ao``, ``ai`` will be set to the same value. - # Import basic softioc framework - from softioc import softioc, builder +.. literalinclude:: ../examples/example_cothread_ioc.py + :start-after: # Boilerplate + :end-before: # Start - # Create PVs - builder.SetDeviceName('TS-TEST-TEST-01') - builder.stringIn('TEST', initial_value = 'This is a test') +Once PVs have been created then the associated EPICS database can be created +and loaded into the IOC and then the IOC can be started. - # Run the IOC. This is boilerplate, and must always be done in this order, - # and must always be done after creating all PVs. - builder.LoadDatabase() - softioc.iocInit() +.. literalinclude:: ../examples/example_cothread_ioc.py + :start-after: # Start + :end-before: # Finally - softioc.interactive_ioc(globals()) +We define a long-running operation that will increment the value of ``ai`` once per +second. This is run as a background process by `cothread`. -This example script illustrates the following points. +.. literalinclude:: ../examples/example_cothread_ioc.py + :start-after: # Finally -- The use of ``pkg_resources.require`` is standard across all use of the - ``dls-python`` Python interpreter at Diamond, and in this example we are using - both :mod:`cothread` and :mod:`epicsdbbuilder`. Of course, in an officially - published IOC specific versions must be specified, in this example I'm using - the most recent versions at the time of writing. +Finally the application must refrain from exiting until the IOC is no longer +needed. The :func:`~softioc.softioc.interactive_ioc` runs a Python +interpreter shell with a number of useful EPICS functions in scope, and +passing ``globals()`` through can allow interactive interaction with the +internals of the IOC while it's running. The alternative is to call something +like :func:`cothread.WaitForQuit` or some other :mod:`cothread` blocking +action. -- The :mod:`softioc` library is part of ``pythonIoc`` and is automatically added - to the path. The two submodules :mod:`softioc.softioc` and - :mod:`softioc.builder` provide the basic functionality for Python soft IOCs - and are the ones that are normally used. +In this interpreter there is immediate access to methods defined in the +:mod:`softioc.softioc` module. For example the :func:`~softioc.softioc.dbgf` function +can be run to observe the increasing value of ``ai``:: -- PVs are normally created dynamically using :mod:`softioc.builder`. All PV - creation must be done before initialising the IOC. + >>> dbgf("MY-DEVICE-PREFIX:AI") + DBF_DOUBLE: 36 + >>> dbgf("MY-DEVICE-PREFIX:AI") + DBF_DOUBLE: 37 -- Once PVs have been created then the associated EPICS database can be created - and loaded into the IOC and then the IOC can be started. +And the :func:`~softioc.softioc.dbpf` method allows data to be set and to observe +the functionality of the lambda passed to `on_update` . We set the value on ``ao`` +and read the value on ``ai`` (exact values may vary based on time between commands):: -- Finally the application must refrain from exiting until the IOC is no longer - needed. The :func:`~softioc.softioc.interactive_ioc` runs a Python - interpreter shell with a number of useful EPICS functions in scope, and - passing ``globals()`` through can allow interactive interaction with the - internals of the IOC while it's running. The alternative is to call something - like :func:`cothread.WaitForQuit` or some other :mod:`cothread` blocking - action. + >>> dbpf("MY-DEVICE-PREFIX:AO","999") + DBF_DOUBLE: 999 + >>> dbgf("MY-DEVICE-PREFIX:AI") + DBF_DOUBLE: 1024 Creating a Publishable IOC @@ -247,3 +200,8 @@ done (:class:`cothread.Spawn` is recommended for initiating persistent backgroun activity) the top level script must pause, as as soon as it exits the IOC will exit. Calling :func:`~softioc.softioc.interactive_ioc` is recommended for this as the last statement in the top level script. + + +.. _numpy: http://www.numpy.org/ +.. _cothread: https://github.com/dls-controls/cothread +.. _epicsdbbuilder: https://github.com/Araneidae/epicsdbbuilder \ No newline at end of file From 3708beac2616664c35dbf8d792b7e619f1b992f7 Mon Sep 17 00:00:00 2001 From: AlexWells Date: Mon, 28 Jun 2021 09:16:18 +0100 Subject: [PATCH 03/14] First draft of rewritten creating-an-ioc Includes extracting out the creation of publishable IOC to a separate how-to page (which is currently just a copy-paste of the text from the tutorial page) --- docs/how-to/make-publishable-ioc.rst | 65 ++++++++++++++++ docs/index.rst | 1 + docs/tutorials/creating-an-ioc.rst | 109 +++++---------------------- 3 files changed, 84 insertions(+), 91 deletions(-) create mode 100644 docs/how-to/make-publishable-ioc.rst diff --git a/docs/how-to/make-publishable-ioc.rst b/docs/how-to/make-publishable-ioc.rst new file mode 100644 index 00000000..e32ff133 --- /dev/null +++ b/docs/how-to/make-publishable-ioc.rst @@ -0,0 +1,65 @@ +Create a Publishable IOC +-------------------------- + +As the example script above shows, a single Python script can be an IOC. +However, to fit into the DLS framework for publishing IOCs in ``/dls_sw/prod`` a +bit more structure is needed. I recommend at least four files as shown: + +``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: + +``start-ioc`` + An executable file for starting the IOC needs to be created. I recommend + that this consist of the following boilerplate:: + + #!/bin/sh + + PYIOC_VER=2-6 + EPICS_VER=3.14.12.3 + + PYIOC=/dls_sw/prod/R$EPICS_VER/support/pythonSoftIoc/$PYIOC_VER/pythonIoc + + exec $PYIOC ioc_entry.py "$@" + + Here I have given the startup script for the IOC the name ``ioc_entry.py``. + This name should be replaced by any appropriate name. + +``ioc_entry.py`` + I recommend that the top level Python script used to launch the IOC contain + only ``pkg_resources.require`` statements, simple code to start the body + of the IOC, and it should end with standard code to start the IOC. The + following structure can be followed (here I've assumed that the rest of the + IOC is in a single file called ``ioc_body.py``:: + + from pkg_resources import require + + require('cothread==2.12') + require('epicsdbbuilder==1.0') + # Any other requires needed by this IOC + + from softioc import softioc + + # Do whatever makes sense to create all the PVs and get ready to go + import ioc_body + ioc_body.initialise() + + # Start the IOC -- this is boilerplate + builder.LoadDatabase() + softioc.iocInit() + + # If activities need to be started after iocInit, now's the time + ioc_body.start() + + softioc.interactive_ioc(globals()) + + Note that *all* requires *must* occur in this initial startup file. + +The rest of the IOC + Of course, a Python script can be structured into any number of Python + modules. In the example above I have illustrated just one such module + called ``ioc_body.py`` with two entry points. \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index c17499c9..f3cfe925 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -53,6 +53,7 @@ About the documentation :maxdepth: 1 how-to/use-asyncio-in-an-ioc + how-to/make-publishable-ioc .. toctree:: :caption: Explanations diff --git a/docs/tutorials/creating-an-ioc.rst b/docs/tutorials/creating-an-ioc.rst index 7c44388e..ee49f448 100644 --- a/docs/tutorials/creating-an-ioc.rst +++ b/docs/tutorials/creating-an-ioc.rst @@ -13,7 +13,7 @@ with two Process Variables (PVs): .. literalinclude:: ../examples/example_cothread_ioc.py -This example script illustrates the following points. +Each section is explained in detail below: .. literalinclude:: ../examples/example_cothread_ioc.py :start-after: # Import @@ -23,21 +23,30 @@ The :mod:`softioc` library is part of ``pythonIoc``. The two submodules :mod:`softioc.softioc` and :mod:`softioc.builder` provide the basic functionality for Python soft IOCs and are the ones that are normally used. +:mod:`cothread` is one of the two possible libraries the IOC can use for +asynchronous operations. +(see :doc:`../how-to/use-asyncio-in-an-ioc` for the other option) + + .. literalinclude:: ../examples/example_cothread_ioc.py :start-after: # Create :end-before: # Boilerplate PVs are normally created dynamically using :mod:`softioc.builder`. All PV -creation must be done before initialising the IOC. We define `on_update` for -``ao`` such that whenever we set ``ao``, ``ai`` will be set to the same value. +creation must be done before initialising the IOC. We define a lambda function for +`on_update` on ``ao`` such that whenever we set ``ao``, ``ai`` will be set to the +same value. .. literalinclude:: ../examples/example_cothread_ioc.py :start-after: # Boilerplate :end-before: # Start -Once PVs have been created then the associated EPICS database can be created -and loaded into the IOC and then the IOC can be started. + +Initializing the IOC is simply a matter of calling two functions: +:func:`~softioc.builder.LoadDatabase` and :func:`~softioc.softioc.iocInit`, +which must be called in this order. After calling +:func:`~softioc.builder.LoadDatabase` it is no longer possible to create PVs. .. literalinclude:: ../examples/example_cothread_ioc.py :start-after: # Start @@ -68,79 +77,14 @@ can be run to observe the increasing value of ``ai``:: And the :func:`~softioc.softioc.dbpf` method allows data to be set and to observe the functionality of the lambda passed to `on_update` . We set the value on ``ao`` -and read the value on ``ai`` (exact values may vary based on time between commands):: +and read the value on ``ai`` (exact values will vary based on time taken):: + >>> dbgf("MY-DEVICE-PREFIX:AI") + DBF_DOUBLE: 15 >>> dbpf("MY-DEVICE-PREFIX:AO","999") DBF_DOUBLE: 999 >>> dbgf("MY-DEVICE-PREFIX:AI") - DBF_DOUBLE: 1024 - - -Creating a Publishable IOC --------------------------- - -As the example script above shows, a single Python script can be an IOC. -However, to fit into the DLS framework for publishing IOCs in ``/dls_sw/prod`` a -bit more structure is needed. I recommend at least four files as shown: - -``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: - -``start-ioc`` - An executable file for starting the IOC needs to be created. I recommend - that this consist of the following boilerplate:: - - #!/bin/sh - - PYIOC_VER=2-6 - EPICS_VER=3.14.12.3 - - PYIOC=/dls_sw/prod/R$EPICS_VER/support/pythonSoftIoc/$PYIOC_VER/pythonIoc - - exec $PYIOC ioc_entry.py "$@" - - Here I have given the startup script for the IOC the name ``ioc_entry.py``. - This name should be replaced by any appropriate name. - -``ioc_entry.py`` - I recommend that the top level Python script used to launch the IOC contain - only ``pkg_resources.require`` statements, simple code to start the body - of the IOC, and it should end with standard code to start the IOC. The - following structure can be followed (here I've assumed that the rest of the - IOC is in a single file called ``ioc_body.py``:: - - from pkg_resources import require - - require('cothread==2.12') - require('epicsdbbuilder==1.0') - # Any other requires needed by this IOC - - from softioc import softioc - - # Do whatever makes sense to create all the PVs and get ready to go - import ioc_body - ioc_body.initialise() - - # Start the IOC -- this is boilerplate - builder.LoadDatabase() - softioc.iocInit() - - # If activities need to be started after iocInit, now's the time - ioc_body.start() - - softioc.interactive_ioc(globals()) - - Note that *all* requires *must* occur in this initial startup file. - -The rest of the IOC - Of course, a Python script can be structured into any number of Python - modules. In the example above I have illustrated just one such module - called ``ioc_body.py`` with two entry points. + DBF_DOUBLE: 1010 Creating PVs @@ -186,22 +130,5 @@ reading and writing the current value of the record. For IN records calling (all IN records are by default created with ``SCAN='I/O Intr'``). -Initialising the IOC --------------------- - -This is simply a matter of calling two functions: -:func:`~softioc.builder.LoadDatabase` and :func:`~softioc.softioc.iocInit`, -which must be called in this order. After calling -:func:`~softioc.builder.LoadDatabase` it is no longer possible to create PVs. - -It is sensible to start any server background activity after the IOC has been -initialised by calling :func:`~softioc.softioc.iocInit`. After this has been -done (:class:`cothread.Spawn` is recommended for initiating persistent background -activity) the top level script must pause, as as soon as it exits the IOC will -exit. Calling :func:`~softioc.softioc.interactive_ioc` is recommended for this -as the last statement in the top level script. - - -.. _numpy: http://www.numpy.org/ .. _cothread: https://github.com/dls-controls/cothread .. _epicsdbbuilder: https://github.com/Araneidae/epicsdbbuilder \ No newline at end of file From f04dbc3f5f281eaf9c410e4100c7f5ec914fec46 Mon Sep 17 00:00:00 2001 From: AlexWells Date: Mon, 28 Jun 2021 14:31:27 +0100 Subject: [PATCH 04/14] Fix README. Add asyncio example. --- README.rst | 30 ++++++++++++++++++++++++++-- docs/examples/example_asyncio_ioc.py | 30 ++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 docs/examples/example_asyncio_ioc.py diff --git a/README.rst b/README.rst index ccb33325..5a21dd4b 100644 --- a/README.rst +++ b/README.rst @@ -15,10 +15,36 @@ 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: -.. literalinclude:: examples/example_cothread_ioc.py +.. code:: python + # 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, 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()) .. |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 diff --git a/docs/examples/example_asyncio_ioc.py b/docs/examples/example_asyncio_ioc.py new file mode 100644 index 00000000..ed0d9d57 --- /dev/null +++ b/docs/examples/example_asyncio_ioc.py @@ -0,0 +1,30 @@ +# Import the basic framework components. +import atexit +from softioc import softioc, builder +from aioca import caget, caput, _catools +import asyncio + +# Set the record prefix +builder.SetDeviceName("MY-DEVICE-PREFIX") + +# Create some records +ai = builder.aIn('AI', initial_value=5) + +async def update(val): + ai.set(val) + +ao = builder.aOut('AO', initial_value=12.45, on_update=update) + +# Boilerplate get the IOC started +builder.LoadDatabase() +softioc.iocInit() + +# Perform some reading/writing to the PVs +async def do_read_write(): + ai_val = await caget("MY-DEVICE-PREFIX:AI") + await caput("MY-DEVICE-PREFIX:AO", "999") + + +asyncio.run(do_read_write()) + + From fcbcfcf9e3d2d72127bb4a25896a33a14f983633 Mon Sep 17 00:00:00 2001 From: AlexWells Date: Tue, 29 Jun 2021 11:45:12 +0100 Subject: [PATCH 05/14] Update the asyncio example file It still doesn't work as I expect - the second caget on AI always returns 5.0, not 999... --- docs/examples/example_asyncio_ioc.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/docs/examples/example_asyncio_ioc.py b/docs/examples/example_asyncio_ioc.py index ed0d9d57..6208caf6 100644 --- a/docs/examples/example_asyncio_ioc.py +++ b/docs/examples/example_asyncio_ioc.py @@ -1,7 +1,6 @@ # Import the basic framework components. -import atexit from softioc import softioc, builder -from aioca import caget, caput, _catools +from aioca import caget, caput import asyncio # Set the record prefix @@ -10,7 +9,9 @@ # Create some records ai = builder.aIn('AI', initial_value=5) -async def update(val): +def update(val): + print("got here") + print(val) ai.set(val) ao = builder.aOut('AO', initial_value=12.45, on_update=update) @@ -21,10 +22,21 @@ async def update(val): # Perform some reading/writing to the PVs async def do_read_write(): - ai_val = await caget("MY-DEVICE-PREFIX:AI") + print(await caget("MY-DEVICE-PREFIX:AO")) + print(await caget("MY-DEVICE-PREFIX:AI")) await caput("MY-DEVICE-PREFIX:AO", "999") + await asyncio.sleep(10) + print(await caget("MY-DEVICE-PREFIX:AI")) + print(await caget("MY-DEVICE-PREFIX:AO")) +#asyncio.run(do_read_write()) -asyncio.run(do_read_write()) +print(asyncio.run(caget("MY-DEVICE-PREFIX:AI"))) +print(asyncio.run(caget("MY-DEVICE-PREFIX:AO"))) +print(asyncio.run(caput("MY-DEVICE-PREFIX:AO","999"))) +print(asyncio.run(caget("MY-DEVICE-PREFIX:AI"))) +print(asyncio.run(caget("MY-DEVICE-PREFIX:AO"))) + +softioc.interactive_ioc(globals()) From 7a32999ca661d55566fd4610db72e952abf4679c Mon Sep 17 00:00:00 2001 From: Tom Cobb Date: Tue, 29 Jun 2021 16:25:06 +0100 Subject: [PATCH 06/14] Updated examples --- docs/examples/example_asyncio_ioc.py | 40 ++++++++++----------------- docs/examples/example_cothread_ioc.py | 4 +-- setup.cfg | 2 +- tests/test_asyncio.py | 5 +++- 4 files changed, 21 insertions(+), 30 deletions(-) diff --git a/docs/examples/example_asyncio_ioc.py b/docs/examples/example_asyncio_ioc.py index 6208caf6..615e3fc7 100644 --- a/docs/examples/example_asyncio_ioc.py +++ b/docs/examples/example_asyncio_ioc.py @@ -1,42 +1,30 @@ # Import the basic framework components. -from softioc import softioc, builder +from softioc import softioc, builder, asyncio_dispatcher from aioca import caget, caput 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) - -def update(val): - print("got here") - print(val) - ai.set(val) - -ao = builder.aOut('AO', initial_value=12.45, on_update=update) +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() - -# Perform some reading/writing to the PVs -async def do_read_write(): - print(await caget("MY-DEVICE-PREFIX:AO")) - print(await caget("MY-DEVICE-PREFIX:AI")) - await caput("MY-DEVICE-PREFIX:AO", "999") - await asyncio.sleep(10) - print(await caget("MY-DEVICE-PREFIX:AI")) - print(await caget("MY-DEVICE-PREFIX:AO")) - -#asyncio.run(do_read_write()) - -print(asyncio.run(caget("MY-DEVICE-PREFIX:AI"))) -print(asyncio.run(caget("MY-DEVICE-PREFIX:AO"))) -print(asyncio.run(caput("MY-DEVICE-PREFIX:AO","999"))) +softioc.iocInit(dispatcher) -print(asyncio.run(caget("MY-DEVICE-PREFIX:AI"))) -print(asyncio.run(caget("MY-DEVICE-PREFIX:AO"))) +# 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()) diff --git a/docs/examples/example_cothread_ioc.py b/docs/examples/example_cothread_ioc.py index 004035e8..7be1c656 100644 --- a/docs/examples/example_cothread_ioc.py +++ b/docs/examples/example_cothread_ioc.py @@ -7,7 +7,8 @@ # Create some records ai = builder.aIn('AI', initial_value=5) -ao = builder.aOut('AO', initial_value=12.45, on_update=lambda v: ai.set(v)) +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() @@ -19,7 +20,6 @@ def update(): ai.set(ai.get() + 1) cothread.Sleep(1) - cothread.Spawn(update) # Finally leave the IOC running with an interactive shell. diff --git a/setup.cfg b/setup.cfg index 9a7b883e..54e180de 100644 --- a/setup.cfg +++ b/setup.cfg @@ -40,7 +40,7 @@ extend-ignore = [tool:pytest] # Run pytest with all our checkers, and don't spam us with massive tracebacks on error -addopts = --tb=native -vv --doctest-modules --ignore=iocStats --ignore=epicscorelibs +addopts = --tb=native -vv --doctest-modules --ignore=iocStats --ignore=epicscorelibs --ignore=docs [coverage:run] # This is covered in the versiongit test suite so exclude it here diff --git a/tests/test_asyncio.py b/tests/test_asyncio.py index 2b1cde28..56d63a6a 100644 --- a/tests/test_asyncio.py +++ b/tests/test_asyncio.py @@ -34,7 +34,10 @@ def asyncio_ioc(): async def test_asyncio_ioc(asyncio_ioc): import asyncio from aioca import caget, caput, camonitor, CANothing, _catools - # Unregister the atexit handler as it conflicts with cothread + # Unregister the aioca atexit handler as it conflicts with the one installed + # by cothread. If we don't do this we get a seg fault. This is not a problem + # in production as we won't mix aioca and cothread, but we do mix them in + # the tests so need to do this. atexit.unregister(_catools._catools_atexit) # Start From b5c3e1bc5fe71f5c980b20be9a73bec752e5dbf1 Mon Sep 17 00:00:00 2001 From: AlexWells Date: Wed, 30 Jun 2021 09:36:10 +0100 Subject: [PATCH 07/14] Finish publish ioc and use asyncio pages. Add a docstring to AsyncioDispatcher.__init__ to suppress the docstring from threading.Thread, which is misleading. --- docs/conf.py | 1 + docs/examples/example_asyncio_ioc.py | 1 - docs/how-to/make-publishable-ioc.rst | 78 +++++++++------------------ docs/how-to/use-asyncio-in-an-ioc.rst | 30 ++++++++++- softioc/asyncio_dispatcher.py | 3 ++ 5 files changed, 57 insertions(+), 56 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 10f60509..f5e72cac 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -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) ) diff --git a/docs/examples/example_asyncio_ioc.py b/docs/examples/example_asyncio_ioc.py index 615e3fc7..d2c99524 100644 --- a/docs/examples/example_asyncio_ioc.py +++ b/docs/examples/example_asyncio_ioc.py @@ -1,6 +1,5 @@ # Import the basic framework components. from softioc import softioc, builder, asyncio_dispatcher -from aioca import caget, caput import asyncio # Create an asyncio dispatcher, the event loop is now running diff --git a/docs/how-to/make-publishable-ioc.rst b/docs/how-to/make-publishable-ioc.rst index e32ff133..7416dd00 100644 --- a/docs/how-to/make-publishable-ioc.rst +++ b/docs/how-to/make-publishable-ioc.rst @@ -1,65 +1,35 @@ Create a Publishable IOC --------------------------- +======================== -As the example script above shows, a single Python script can be an IOC. -However, to fit into the DLS framework for publishing IOCs in ``/dls_sw/prod`` a -bit more structure is needed. I recommend at least four files as shown: +As seen in :doc:`../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. -``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: - -``start-ioc`` - An executable file for starting the IOC needs to be created. I recommend - that this consist of the following boilerplate:: - - #!/bin/sh - - PYIOC_VER=2-6 - EPICS_VER=3.14.12.3 - - PYIOC=/dls_sw/prod/R$EPICS_VER/support/pythonSoftIoc/$PYIOC_VER/pythonIoc +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:: - exec $PYIOC ioc_entry.py "$@" + #!/dls_sw/prod/python3/RHEL7-x86_64/pythonIoc/prefix/bin/pythonIoc - Here I have given the startup script for the IOC the name ``ioc_entry.py``. - This name should be replaced by any appropriate name. -``ioc_entry.py`` - I recommend that the top level Python script used to launch the IOC contain - only ``pkg_resources.require`` statements, simple code to start the body - of the IOC, and it should end with standard code to start the IOC. The - following structure can be followed (here I've assumed that the rest of the - IOC is in a single file called ``ioc_body.py``:: +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. - from pkg_resources import require - require('cothread==2.12') - require('epicsdbbuilder==1.0') - # Any other requires needed by this IOC +Make an IOC publishable at DLS +------------------------------ +To make the IOC publishable, a makefile is required: - from softioc import softioc - - # Do whatever makes sense to create all the PVs and get ready to go - import ioc_body - ioc_body.initialise() - - # Start the IOC -- this is boilerplate - builder.LoadDatabase() - softioc.iocInit() - - # If activities need to be started after iocInit, now's the time - ioc_body.start() - - softioc.interactive_ioc(globals()) +``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:: - Note that *all* requires *must* occur in this initial startup file. + install: + clean: -The rest of the IOC - Of course, a Python script can be structured into any number of Python - modules. In the example above I have illustrated just one such module - called ``ioc_body.py`` with two entry points. \ No newline at end of file diff --git a/docs/how-to/use-asyncio-in-an-ioc.rst b/docs/how-to/use-asyncio-in-an-ioc.rst index 016d0462..67a4af27 100644 --- a/docs/how-to/use-asyncio-in-an-ioc.rst +++ b/docs/how-to/use-asyncio-in-an-ioc.rst @@ -1,4 +1,32 @@ Use `asyncio` in an IOC ======================= -Write about the differences creating an IOC using `AsyncioDispatcher` +There are two libraries available for asynchronous operations in PythonIOC: +:mod:`cothread` and :mod:`asyncio`. This guide shows how to use the latter in +an IOC. + +.. note:: + This page only explains the differences between using :mod:`cothread` and :mod:`asyncio`. + For more thorough explanation of the IOC itself see :doc:`../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 :mod:`asyncio` functions in this IOC. + +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 :doc:`../tutorials/creating-an-ioc`, leave an interactive +shell open. The values of the PVs can be queried using the methods defined in the +:mod:`softioc.softioc` module. + + +Asynchronous Channel Access +--------------------------- + +PVs can be retrieved in an asynchronous manner by using the :py:mod:`aioca` module. +It provides ``await``-able implementations of ``caget``, ``caput``, etc. diff --git a/softioc/asyncio_dispatcher.py b/softioc/asyncio_dispatcher.py index 2ee7246f..598120e8 100644 --- a/softioc/asyncio_dispatcher.py +++ b/softioc/asyncio_dispatcher.py @@ -9,6 +9,9 @@ class AsyncioDispatcher(threading.Thread): created. """ def __init__(self): + """Create the AsyncioDispatcher.""" + # Docstring specified to suppress threading.Thread's docstring, which + # would otherwise be inherited by this method and be misleading. super().__init__() #: `asyncio` event loop that the callbacks will run under. self.loop = asyncio.new_event_loop() From 4d7f891dc7e6f4b88a5850529ff3537e8a172d38 Mon Sep 17 00:00:00 2001 From: AlexWells Date: Wed, 30 Jun 2021 11:50:06 +0100 Subject: [PATCH 08/14] Correct the shebang line based on latest published version --- docs/how-to/make-publishable-ioc.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/make-publishable-ioc.rst b/docs/how-to/make-publishable-ioc.rst index 7416dd00..e8559b39 100644 --- a/docs/how-to/make-publishable-ioc.rst +++ b/docs/how-to/make-publishable-ioc.rst @@ -11,7 +11,7 @@ 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/pythonIoc/prefix/bin/pythonIoc + #!/dls_sw/prod/python3/RHEL7-x86_64/softioc/3.0b2/prefix/bin/pythonIoc IOC entry point for a module From ebe736b294dae5e672abb6b4c96cd15bb0741dc5 Mon Sep 17 00:00:00 2001 From: AlexWells Date: Thu, 1 Jul 2021 15:56:38 +0100 Subject: [PATCH 09/14] New how-to read from an IOC. Some touchups to other work. --- docs/examples/example_read_from_ioc.py | 12 ++++++++++ docs/how-to/read-data-from-ioc.rst | 31 ++++++++++++++++++++++++++ docs/how-to/use-asyncio-in-an-ioc.rst | 7 +++--- docs/index.rst | 1 + 4 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 docs/examples/example_read_from_ioc.py create mode 100644 docs/how-to/read-data-from-ioc.rst diff --git a/docs/examples/example_read_from_ioc.py b/docs/examples/example_read_from_ioc.py new file mode 100644 index 00000000..f7a1a946 --- /dev/null +++ b/docs/examples/example_read_from_ioc.py @@ -0,0 +1,12 @@ +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")) + +def print_val(value: float): + print(value) + +softioc.interactive_ioc(globals()) diff --git a/docs/how-to/read-data-from-ioc.rst b/docs/how-to/read-data-from-ioc.rst new file mode 100644 index 00000000..c56b16cc --- /dev/null +++ b/docs/how-to/read-data-from-ioc.rst @@ -0,0 +1,31 @@ +Read data from an IOC +====================== + +This guide explains how to read data from an IOC from 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 :mod:`cothread` IOC from :doc:`../tutorials/creating-an-ioc` or the +:mod:`asyncio` IOC from :doc:`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", print_val) + + +You should observe the value of ``AI`` being printed out, once per second, every time the PVs value +updates. \ No newline at end of file diff --git a/docs/how-to/use-asyncio-in-an-ioc.rst b/docs/how-to/use-asyncio-in-an-ioc.rst index 67a4af27..e39269c5 100644 --- a/docs/how-to/use-asyncio-in-an-ioc.rst +++ b/docs/how-to/use-asyncio-in-an-ioc.rst @@ -13,7 +13,8 @@ an IOC. The ``dispatcher`` is created and passed to :func:`~softioc.softioc.iocInit`. This is what -allows the use of :mod:`asyncio` functions in this IOC. +allows the use of :mod:`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. @@ -28,5 +29,5 @@ shell open. The values of the PVs can be queried using the methods defined in th Asynchronous Channel Access --------------------------- -PVs can be retrieved in an asynchronous manner by using the :py:mod:`aioca` module. -It provides ``await``-able implementations of ``caget``, ``caput``, etc. +PVs can be retrieved externally from a PV in an asynchronous manner by using the :py:mod:`aioca` module. +It provides ``await``-able implementations of ``caget``, ``caput``, etc. See that module for more information. diff --git a/docs/index.rst b/docs/index.rst index 0f518592..56b06846 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -60,6 +60,7 @@ Table Of Contents how-to/use-asyncio-in-an-ioc how-to/make-publishable-ioc + how-to/read-data-from-ioc .. toctree:: :caption: Explanations From 457ea8d1df7660ed23dcc2f3c9652b0500200151 Mon Sep 17 00:00:00 2001 From: AlexWells Date: Thu, 1 Jul 2021 16:04:43 +0100 Subject: [PATCH 10/14] Use a lambda rather than the defined function. --- docs/examples/example_read_from_ioc.py | 3 --- docs/how-to/read-data-from-ioc.rst | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/examples/example_read_from_ioc.py b/docs/examples/example_read_from_ioc.py index f7a1a946..1834d303 100644 --- a/docs/examples/example_read_from_ioc.py +++ b/docs/examples/example_read_from_ioc.py @@ -6,7 +6,4 @@ print(caput("MY-DEVICE-PREFIX:AO", "999")) print(caget("MY-DEVICE-PREFIX:AO")) -def print_val(value: float): - print(value) - softioc.interactive_ioc(globals()) diff --git a/docs/how-to/read-data-from-ioc.rst b/docs/how-to/read-data-from-ioc.rst index c56b16cc..38939f5d 100644 --- a/docs/how-to/read-data-from-ioc.rst +++ b/docs/how-to/read-data-from-ioc.rst @@ -24,8 +24,8 @@ We will read data from that IOC using this script: 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", print_val) + 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 PVs value +You should observe the value of ``AI`` being printed out, once per second, every time the PV value updates. \ No newline at end of file From 06ca7777f17bd7f3dc50f4526d0eea97379dd068 Mon Sep 17 00:00:00 2001 From: AlexWells Date: Thu, 1 Jul 2021 16:20:17 +0100 Subject: [PATCH 11/14] Minor grammatical changes from proof-reading --- docs/explanations/why-use-pythonIoc.rst | 2 -- docs/how-to/read-data-from-ioc.rst | 2 +- docs/tutorials/creating-an-ioc.rst | 15 +++++++++------ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/explanations/why-use-pythonIoc.rst b/docs/explanations/why-use-pythonIoc.rst index c15a6368..d1b74104 100644 --- a/docs/explanations/why-use-pythonIoc.rst +++ b/docs/explanations/why-use-pythonIoc.rst @@ -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 ----------------------- diff --git a/docs/how-to/read-data-from-ioc.rst b/docs/how-to/read-data-from-ioc.rst index 38939f5d..ea9284c4 100644 --- a/docs/how-to/read-data-from-ioc.rst +++ b/docs/how-to/read-data-from-ioc.rst @@ -1,7 +1,7 @@ Read data from an IOC ====================== -This guide explains how to read data from an IOC from a separate Python program. +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. diff --git a/docs/tutorials/creating-an-ioc.rst b/docs/tutorials/creating-an-ioc.rst index ee49f448..26fbda88 100644 --- a/docs/tutorials/creating-an-ioc.rst +++ b/docs/tutorials/creating-an-ioc.rst @@ -25,7 +25,7 @@ functionality for Python soft IOCs and are the ones that are normally used. :mod:`cothread` is one of the two possible libraries the IOC can use for asynchronous operations. -(see :doc:`../how-to/use-asyncio-in-an-ioc` for the other option) +(see :doc:`../how-to/use-asyncio-in-an-ioc` for the alternative) @@ -36,7 +36,10 @@ asynchronous operations. PVs are normally created dynamically using :mod:`softioc.builder`. All PV creation must be done before initialising the IOC. We define a lambda function for `on_update` on ``ao`` such that whenever we set ``ao``, ``ai`` will be set to the -same value. +same value. The ``always_update`` flag ensures that the ``on_update`` function is always +triggered, which is not the default behaviour if the updated value is the same as the +current value. + .. literalinclude:: ../examples/example_cothread_ioc.py :start-after: # Boilerplate @@ -53,7 +56,7 @@ which must be called in this order. After calling :end-before: # Finally We define a long-running operation that will increment the value of ``ai`` once per -second. This is run as a background process by `cothread`. +second. This is run as a background thread by `cothread`. .. literalinclude:: ../examples/example_cothread_ioc.py :start-after: # Finally @@ -68,7 +71,7 @@ action. In this interpreter there is immediate access to methods defined in the :mod:`softioc.softioc` module. For example the :func:`~softioc.softioc.dbgf` function -can be run to observe the increasing value of ``ai``:: +can be run to observe the increasing value of ``AI``:: >>> dbgf("MY-DEVICE-PREFIX:AI") DBF_DOUBLE: 36 @@ -76,8 +79,8 @@ can be run to observe the increasing value of ``ai``:: DBF_DOUBLE: 37 And the :func:`~softioc.softioc.dbpf` method allows data to be set and to observe -the functionality of the lambda passed to `on_update` . We set the value on ``ao`` -and read the value on ``ai`` (exact values will vary based on time taken):: +the functionality of the lambda passed to ``on_update`` . We set the value on ``AO`` +and read the value on ``AI`` (exact values will vary based on time taken):: >>> dbgf("MY-DEVICE-PREFIX:AI") DBF_DOUBLE: 15 From 26cb48008e722cf346f3c04d023ba6228bee8ec3 Mon Sep 17 00:00:00 2001 From: AlexWells Date: Thu, 1 Jul 2021 16:25:22 +0100 Subject: [PATCH 12/14] Docstring update for AsyncioDispatcher --- softioc/asyncio_dispatcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/softioc/asyncio_dispatcher.py b/softioc/asyncio_dispatcher.py index 598120e8..c992dd0e 100644 --- a/softioc/asyncio_dispatcher.py +++ b/softioc/asyncio_dispatcher.py @@ -9,7 +9,7 @@ class AsyncioDispatcher(threading.Thread): created. """ def __init__(self): - """Create the AsyncioDispatcher.""" + """Create an AsyncioDispatcher suitable to be used by `softioc.iocInit`.""" # Docstring specified to suppress threading.Thread's docstring, which # would otherwise be inherited by this method and be misleading. super().__init__() From 338be4d84ecf4bc79a7838f4abaee4884dcd0a2c Mon Sep 17 00:00:00 2001 From: AlexWells Date: Thu, 1 Jul 2021 16:30:55 +0100 Subject: [PATCH 13/14] Fix linting error --- softioc/asyncio_dispatcher.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/softioc/asyncio_dispatcher.py b/softioc/asyncio_dispatcher.py index c992dd0e..c85ceefb 100644 --- a/softioc/asyncio_dispatcher.py +++ b/softioc/asyncio_dispatcher.py @@ -9,7 +9,8 @@ class AsyncioDispatcher(threading.Thread): created. """ def __init__(self): - """Create an AsyncioDispatcher suitable to be used by `softioc.iocInit`.""" + """Create an AsyncioDispatcher suitable to be used by + `softioc.iocInit`.""" # Docstring specified to suppress threading.Thread's docstring, which # would otherwise be inherited by this method and be misleading. super().__init__() From a873d0e14ee4a99a47daf8da43f2ccbdaa5c3c4f Mon Sep 17 00:00:00 2001 From: AlexWells Date: Fri, 2 Jul 2021 08:58:59 +0100 Subject: [PATCH 14/14] Remove all unncecessary :role: tags that I can find. --- docs/how-to/make-publishable-ioc.rst | 2 +- docs/how-to/read-data-from-ioc.rst | 4 ++-- docs/how-to/use-asyncio-in-an-ioc.rst | 14 +++++++------- docs/reference/api.rst | 2 +- docs/tutorials/creating-an-ioc.rst | 28 ++++++++++++--------------- 5 files changed, 23 insertions(+), 27 deletions(-) diff --git a/docs/how-to/make-publishable-ioc.rst b/docs/how-to/make-publishable-ioc.rst index e8559b39..f9ee37df 100644 --- a/docs/how-to/make-publishable-ioc.rst +++ b/docs/how-to/make-publishable-ioc.rst @@ -1,7 +1,7 @@ Create a Publishable IOC ======================== -As seen in :doc:`../tutorials/creating-an-ioc`, a single Python script can be an 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. diff --git a/docs/how-to/read-data-from-ioc.rst b/docs/how-to/read-data-from-ioc.rst index ea9284c4..35196dd3 100644 --- a/docs/how-to/read-data-from-ioc.rst +++ b/docs/how-to/read-data-from-ioc.rst @@ -8,8 +8,8 @@ This guide explains how to read data from an IOC in a separate Python program. These are used by EPICS for channel access to the PVs. -To start, run the :mod:`cothread` IOC from :doc:`../tutorials/creating-an-ioc` or the -:mod:`asyncio` IOC from :doc:`use-asyncio-in-an-ioc` and leave it running at the +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: diff --git a/docs/how-to/use-asyncio-in-an-ioc.rst b/docs/how-to/use-asyncio-in-an-ioc.rst index e39269c5..4debe46e 100644 --- a/docs/how-to/use-asyncio-in-an-ioc.rst +++ b/docs/how-to/use-asyncio-in-an-ioc.rst @@ -2,18 +2,18 @@ Use `asyncio` in an IOC ======================= There are two libraries available for asynchronous operations in PythonIOC: -:mod:`cothread` and :mod:`asyncio`. This guide shows how to use the latter in +`cothread` and `asyncio`. This guide shows how to use the latter in an IOC. .. note:: - This page only explains the differences between using :mod:`cothread` and :mod:`asyncio`. - For more thorough explanation of the IOC itself see :doc:`../tutorials/creating-an-ioc` + 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 :mod:`asyncio` functions in this IOC. It contains a new event loop to handle +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, @@ -21,13 +21,13 @@ 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 :doc:`../tutorials/creating-an-ioc`, leave an interactive +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 -:mod:`softioc.softioc` module. +`softioc.softioc` module. Asynchronous Channel Access --------------------------- -PVs can be retrieved externally from a PV in an asynchronous manner by using the :py:mod:`aioca` module. +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. diff --git a/docs/reference/api.rst b/docs/reference/api.rst index 4acc2830..2f305ef5 100644 --- a/docs/reference/api.rst +++ b/docs/reference/api.rst @@ -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 diff --git a/docs/tutorials/creating-an-ioc.rst b/docs/tutorials/creating-an-ioc.rst index 26fbda88..62cbbb5b 100644 --- a/docs/tutorials/creating-an-ioc.rst +++ b/docs/tutorials/creating-an-ioc.rst @@ -4,10 +4,10 @@ Creating an IOC Introduction ------------ -Once the module has been installed (see :doc:`installation`) we can create a +Once the module has been installed (see `installation`) we can create a simple EPICS Input/Output Controller (IOC). -An EPICS IOC created with the help of ``pythonIoc`` and :mod:`softioc` is +An EPICS IOC created with the help of ``pythonIoc`` and `softioc` is referred to as a "Python soft IOC". The code below illustrates a simple IOC with two Process Variables (PVs): @@ -19,13 +19,13 @@ Each section is explained in detail below: :start-after: # Import :end-before: # Set -The :mod:`softioc` library is part of ``pythonIoc``. The two submodules -:mod:`softioc.softioc` and :mod:`softioc.builder` provide the basic +The `softioc` library is part of ``pythonIoc``. The two submodules +`softioc.softioc` and `softioc.builder` provide the basic functionality for Python soft IOCs and are the ones that are normally used. -:mod:`cothread` is one of the two possible libraries the IOC can use for +`cothread` is one of the two possible libraries the IOC can use for asynchronous operations. -(see :doc:`../how-to/use-asyncio-in-an-ioc` for the alternative) +(see `../how-to/use-asyncio-in-an-ioc` for the alternative) @@ -33,7 +33,7 @@ asynchronous operations. :start-after: # Create :end-before: # Boilerplate -PVs are normally created dynamically using :mod:`softioc.builder`. All PV +PVs are normally created dynamically using `softioc.builder`. All PV creation must be done before initialising the IOC. We define a lambda function for `on_update` on ``ao`` such that whenever we set ``ao``, ``ai`` will be set to the same value. The ``always_update`` flag ensures that the ``on_update`` function is always @@ -66,11 +66,11 @@ needed. The :func:`~softioc.softioc.interactive_ioc` runs a Python interpreter shell with a number of useful EPICS functions in scope, and passing ``globals()`` through can allow interactive interaction with the internals of the IOC while it's running. The alternative is to call something -like :func:`cothread.WaitForQuit` or some other :mod:`cothread` blocking +like :func:`cothread.WaitForQuit` or some other `cothread` blocking action. In this interpreter there is immediate access to methods defined in the -:mod:`softioc.softioc` module. For example the :func:`~softioc.softioc.dbgf` function +`softioc.softioc` module. For example the :func:`~softioc.softioc.dbgf` function can be run to observe the increasing value of ``AI``:: >>> dbgf("MY-DEVICE-PREFIX:AI") @@ -93,12 +93,12 @@ and read the value on ``AI`` (exact values will vary based on time taken):: Creating PVs ------------ -See the documentation of :mod:`softioc.builder` for details, but an overview is +See the documentation of `softioc.builder` for details, but an overview is provided here. PVs are created internally and dynamically using functionality provided by -:mod:`epicsdbbuilder`, which in this context simply provides mechanisms for -creating ``.db`` files, but :mod:`softioc.builder` also binds each created PV to +`epicsdbbuilder`, which in this context simply provides mechanisms for +creating ``.db`` files, but `softioc.builder` also binds each created PV to a special ``Python`` device -- this allows PV processing to be hooked into Python support. @@ -131,7 +131,3 @@ For all records created by these methods both reading and writing the current value of the record. For IN records calling :meth:`~softioc.device.ProcessDeviceSupportIn.set` will trigger a record update (all IN records are by default created with ``SCAN='I/O Intr'``). - - -.. _cothread: https://github.com/dls-controls/cothread -.. _epicsdbbuilder: https://github.com/Araneidae/epicsdbbuilder \ No newline at end of file