Skip to content

Commit

Permalink
Merge remote branch 'john/docs' into docs
Browse files Browse the repository at this point in the history
  • Loading branch information
coretl committed Dec 2, 2016
2 parents b901031 + f6ae040 commit 085eaf5
Showing 1 changed file with 48 additions and 25 deletions.
73 changes: 48 additions & 25 deletions docs/user_docs/generator.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,29 @@ Generator Tutorial

You should already know how to create a `Part` that attaches
`Attribute` and `Method` instances to a `Block`.
The Blocks we have made in previous tutorials are quite simple and low level,
and might correspond to the interface provided by EPICS devices, a collection
The Blocks we have made in previous tutorials are quite simple and low level
and might correspond to the interface provided by EPICS devices: a collection
of Attributes that we can set and simple Methods we can call that cause the
device to operate in a particular way. What is missing is the logic of "do
this, then that, then these 3 things at the same time". To do this, we will
create a higher level Block that will control a number of child Blocks to
synchronise them and use them for a particular application.

Creating Higher Level Blocks
----------------------------

These higher level Blocks have two main methods:

- configure(params): Take a set of parameters, and configure all child Blocks
- configure(params): Take a set of parameters and configure all child Blocks
according to these parameters. This operation should include as much as
possible of the setup of the device, without actually starting a scan.
- run(): When all devices taking part in the scan have configured themselves,
this method with start the scan going. It supervises the actions of the
this method will start the scan going. It supervises the actions of the
scan, providing status monitoring and any periodic actions that need to
happen.

The application we have chosen for this tutorial is a ScanTicker. It will take
the specification for a scan, then use a number of Counter blocks that we saw in
the specification for a scan then use a number of Counter blocks, that we saw in
the last tutorial, setting them to the demand positions of the axes in the
scan. This will look a little like a Motor Controller performing a continuous
scan.
Expand All @@ -39,9 +42,12 @@ of those does:
.. literalinclude:: ../../malcolm/blocks/demo/Ticker.yaml
:language: yaml

We instantiate two Counter blocks, and instantiate two ScanTickerParts that
We instantiate two Counter blocks (``COUNTERX`` and ``COUNTERY``) and instantiate
two ScanTickerParts (``x`` and ``y``) that
will connect to them. We then use a `RunnableController` to construct
our Block. This is probably better viewed as a diagram:
our Block.

This is probably better viewed as a diagram:

.. uml::

Expand Down Expand Up @@ -122,21 +128,21 @@ Specifying Scan Points

If this ScanTicker Block is going to simulate running a scan, we better learn
how to specify a scan. There are a number of pieces of information about each
point in a scan that are needed by parts of Malcolm:
point in a scan that are needed by Malcolm:

- The demand positions of a number of actuators representing where they should
be at the mid-point of a detector frame. This is needed for step scans and
continuous scans.
- The demand positions of those actuators at the upper and lower bounds (start
and end) or that detector frame. This is needed for continuous scans only so
and end) of that detector frame. This is needed for continuous scans only so
that each detector frame is taken while the actuators were moving at a
constant velocity
constant velocity.
- The index in the data file that the frame should be stored. For grid based
scan (like a snake scan), these will have the same dimensions as the demand
positions. For non grid based scans (like a spiral scan), these will have
scans (like a snake scan) these will have the same dimensions as the demand
positions. For non grid based scans (like a spiral scan) these will have
less dimensions because the datapoints do not fit onto a regular grid.
- The duration of the frame. This is needed for continuous scans and is the
time taken to get from the lower to the upper bound
time taken to get from the lower to the upper bound.

The size of each index dimension and units for each actuator are also
needed for file writing.
Expand All @@ -152,7 +158,7 @@ Hooking into configure() and run()

We mentioned earlier that a Part can register functions to run the correct
phase of Methods provided by the Controller. Lets take a look at the first
part of ``parts.demo.scantickerpart.py`` to see how this works:
part of ``./malcolm/parts/demo/scantickerpart.py`` to see how this works:

.. literalinclude:: ../../malcolm/parts/demo/scantickerpart.py
:language: python
Expand All @@ -163,7 +169,9 @@ You'll notice some more decorators on those functions. The
A `Controller` defines a a number of Hooks that define what methods
of a `Part` will be run during a particular `Method`. For
example, we are hooking our ``configure()`` method to the
`Configure` Hook. Let's take a look at its documentation:
`Configure` Hook.

Let's take a look at its documentation:

.. py:currentmodule:: malcolm.controllers
Expand All @@ -174,7 +182,9 @@ What happens in practice is that when ``TICKER.configure()`` is called, all the
functions hooked to `Configure` will be called concurrently. They will each
be passed the five arguments listed in the documentation above. Our ScanTicker
``configure()`` method simply stores the relevant information so that the
``run()`` method can operate on it. Lets look at that next:
``run()`` method can operate on it.

Lets look at that next:

.. literalinclude:: ../../malcolm/parts/demo/scantickerpart.py
:language: python
Expand All @@ -189,9 +199,9 @@ Walking through the code we can see that we are iterating through each of the
step indexes that we need to produce, getting a `scanpointgenerator.Point`
object for each one.
We then pick out the position of the current axis, and use the `Task` to put
the value to the ``counter`` value. It is important that we use ``task`` to
the value to the ``counter`` value. It is important that we use ``task`` parameter to
do this rather than doing ``self.child.counter = value`` because this is
interruptable. The `Task` helper can also do asynchronous puts, and puts to
interruptable. The `Task` helper can also do asynchronous puts and puts to
multiple attributes at the same time.

After we have done the put, we work out how long we need to wait until the
Expand All @@ -204,7 +214,7 @@ is producing the update.

Let's run up the example and give it a go::

[tmc43@diamtr317 pymalcolm]$ ./malcolm/imalcolm.py examples/DEMO-TICKER.yaml
[tmc43@pc0013 pymalcolm]$ ./malcolm/imalcolm.py examples/DEMO-TICKER.yaml
INFO:COUNTERX.reset:I'm not writeable
INFO:COUNTERX:Exception while handling ordereddict([('typeid', 'malcolm:core/Post:1.0'), ('id', 0), ('endpoint', ['COUNTERX', 'reset']), ('parameters', None)])
INFO:COUNTERY.reset:I'm not writeable
Expand Down Expand Up @@ -237,6 +247,10 @@ Let's run up the example and give it a go::
self.process_block.blocks


In [1]:

Then enter::

In [1]: from scanpointgenerator import LineGenerator, CompoundGenerator, FixedDurationMutator

In [2]: ticker = self.get_block("TICKER")
Expand All @@ -261,15 +275,24 @@ Let's run up the example and give it a go::
What we have done here is set up a scan that is 6 rows in y and 5 columns in x.
The x value will snake forwards and backwards, and the y value will increase
at the end of each x row. We have told it that each scan point should last for
0.5 seconds, which should give us enough time to see the ticks. If you now
click the run button on the TICKER window, you should now see a scan performed:
0.5 seconds, which should give us enough time to see the ticks.

If you now click the run button on the TICKER window, you should now see a scan performed:

.. image:: ticker_1.png

From here you can try pausing, resuming and seeking within the scan. What is
happening under the hood is that our hooked ``configure()`` method is being
From here you can try pausing, resuming and seeking within the scan. If you want
to re-run the scan you need to run::

In [11]: ticker.configure(generator, ["x", "y"])

This is because the GUI doesn't yet include an editor for scan specifications. This command
will set the device to ``Ready`` so that you can then ``run()`` again. See `statemachine_diagrams`
for more information about what functions you can run in different Block states.

What is happening under the hood is that our hooked ``configure()`` method is being
called during ``pause()``, ``configure()`` and ``seek()``, but we want it to
do the same thing each time so can use the same method. The ``run()`` command
do the same thing each time so can use the same method. The ``run()`` command
is likewise hooked to both ``run()`` and ``resume()`` as it makes no
difference in our example. In a real example, there may be some device state
that would mean different things need to be run in these two hooks.
Expand All @@ -278,7 +301,7 @@ Conclusion
==========

This tutorial has given us an understanding of how scans are specified
in Malcolm, how child Blocks are controlled from parent Blocks, and how Parts
in Malcolm, how child Blocks are controlled from parent Blocks and how Parts
can register code to run at different phases of a Controller.

.. _Scan Point Generator:
Expand Down

0 comments on commit 085eaf5

Please sign in to comment.