Skip to content
Snippets Groups Projects
Commit 3c2de3b2 authored by benjamin.franksen's avatar benjamin.franksen
Browse files

docs: re-wrote the introductory sections of the tutorial

parent b0a805a9
No related branches found
No related tags found
No related merge requests found
......@@ -4,95 +4,98 @@ Tutorial
This chapter gives a gentle introduction to State Notation Language
(SNL).
Introduction
------------
A First Example
---------------
SNL is a programming language specifically designed for programming
*finite state machines* in such a way that it is easy for the program
to interact with EPICS *process variables* (PVs), allowing to read and
to write them and to react to changes in their value or status.
We start with a simple state machine ``volt_check`` that controls a
light switch depending on the value of a voltage measurement and
the internal state of the program. The following code fragment
defines the state machine::
An SNL program consists of a number of *state sets*, each one
representing an independent state machine. The state set lists all the
possible *states* that are allowed for this state set, along with
conditions for transitions to other states of the same state set and
code that is executed when the state transition take place.
ss volt_check {
state light_off {
when (voltage > 5.0) {
light = TRUE;
pvPut(light);
} state light_on
}
state light_on {
when (voltage < 3.0) {
light = FALSE;
pvPut(light);
} state light_off
}
}
At the top level we use the keyword ``ss`` to declare a *state set* (which
is SNL speak for state machine) named ``volt_check``. Inside the code
block that follows, we define the *states* of this state set, using the
``state`` keyword. There are two states here: ``light_off`` and ``light_on``.
Inside each state, we define conditions under which the program will
enter another state, indicated by the keyword ``when``. The block
following the condition contains *action statements* that are executed
when the condition fires.
State Transition Diagrams
-------------------------
In our example, in state ``light_off``, whenever the voltage exceeds a
value of 5.0, the light switch is turned on, and the internal state
changes to ``light_on``. In state ``light_on``, whenever the voltage is
or drops below 3.0, the light switch is turned off, and the internal
state changes to ``light_off``.
The following is a graphical representation of the above state machine:
A *state transition diagram* (STD) is a graphical notation for
specifying state machines. In the STD, states are represented by oval
boxes, transitions by arrows. State transitions can happen in response
to both the present internal state and some external event or
condition; this is represented by annotating the state transition
arrow with a horizontal bar: the condition is noted above the line and
the action to be taken when the transition happens below the line.
.. image:: Figure2-1.png
A simple STD is shown in Figure 2-1. In this example the level of an
input voltage is sensed; a light is turned on if the voltage is
greater than 5 volts and the state is "Light is Off"; conversely, the
light is turned off if the voltage becomes less than 3 volts and the
state is "Light is On".
In this picture, states are represented by oval boxes, transitions by
arrows. State transitions are annotated with a horizontal bar: the
condition is noted above the line and the action to be taken when the
transition happens below the line.
Note that the output or action depends not only on the input or
condition, but also on the current state. For instance, an input ``v``
of 4.2 volts does not alone determine the output (``light``), the
current state matters, too.
condition, but also on the current state. For instance, an input
``voltage`` of 4.2 volts does not alone determine the output (``light``),
the current state matters, too.
.. image:: Figure2-1.png
As you can see, the SNL code is syntactically very similar to the C
language. Particularly, the syntax for variable declarations,
expressions, and statements are exactly as in C, albeit with a few
restrictions.
You might wonder about the function calls in the above code. The
``pvPut`` function is a special built-in function that writes (or puts)
the value in the variable ``light`` to the appropriate process variable.
But before I can explain how this works, we must talk about how program
variables are "connected" to process variables.
Elements of the State Notation Language
---------------------------------------
The following SNL code segment expresses the STD in Figure 2-1::
Variables
---------
state light_off {
when (v > 5.0) {
light = TRUE;
pvPut(light);
} state light_on
}
SNL programs interact with the outside world via variables that are bound
to (or connected to) *process variables* (PVs) in EPICS. In our example,
there are two such variables: ``voltage``, which represents a measured
voltage, and ``light`` which controls a light switch. In an actual SNL
program, these variables must be declared before they can be used::
state light_on {
when (v < 3.0) {
light = FALSE;
pvPut(light);
} state light_off
}
float voltage;
int light;
You will notice that the SNL appears to have a structure and syntax
that is similar to the C language. In fact the SNL uses its own syntax
plus a subset of C, such as expressions, assignments, and function
calls. This example contains two code blocks that define two states:
``light_off`` and ``light_on``. Within each state block are
:token:`when` clauses that define events or conditions (``v > 5.0``
and ``v < 3.0``) that lead to a state change. Following each condition
is a block containing actions (C statements). The :c:func:`pvPut`
function writes or puts the value in the variable ``light`` to the
appropriate process variable. Following the action block, the next
state is specified.
The variables (in our example ``v`` and ``light``) must be declared
and associated with process variables::
float v;
short light;
assign v to "Input_voltage";
We also want to associated them with PVs i.e. EPICS record fields::
assign voltage to "Input_voltage";
assign light to "Indicator_light";
The above :token:`assign` clause associate the variables ``v`` and
The above :token:`assign` clauses associate the variables ``voltage`` and
``light`` with the process variables "Input_voltage" and
"Indicator_light" respectively. We want the value of ``v`` to be
updated automatically whenever it changes. This is accomplished with
the following declaration::
"Indicator_light" respectively.
We also want the value of ``voltage`` to be updated automatically whenever
it changes. This is accomplished with the following code::
monitor v;
monitor voltage;
Whenever the value in the control system (the EPICS database) changes,
the value of ``v`` will likewise change. Note however that this
the value of ``voltage`` will likewise change. Note however that this
depends on the underlying system sending update messages for the value
in question. When and how often such updates are communicated by the
underlying system may depend on the configuration of the PV. For
......@@ -103,7 +106,41 @@ specifies the amount of change that the designer considers a
and accordingly will not cause a state change in the above program.
.. _A Complete Program:
Built-in PV Functions
---------------------
I said above that the program interacts with the outside world via
variables assigned to PVs. However, mutating such a variable e.g. via
the C assignment operator (see :ref:`Assignment Operators`), as in::
light = TRUE;
only changes the value of ``light`` as seen from inside the program.
In order for the new value to take effect, it must be written to the
PV connected with the variable. This is done by calling the special
built-in function :c:func:`pvPut`, that gets the variable as argument.
Note that calling such a special built-in function does *not* follow
the standard C semantics for function calls! Particularly, what
actually gets passed to the function is not the *value* of the variable
``light`` (as it would in C), instead an internal representation of the
variable gets passed (by reference). You can think of what actually
gets passed as an "object" (as in "object-oriented") or a "handle"
that contains all the necessary run-time information, one of which is
the name of PV the variable is connected with.
There are many more of these built-in functions, the :doc:`Reference`
contains detailed description of each one. For now, let's keep to the
basics; I'll mention just one more built-in function: With
:c:func:`pvGet`, you can poll PVs explicitly, instead of using
:token:`monitor`. That is, a statement such as ::
pvGet(voltage);
has the effect of sending a get request to the PV "Input_voltage",
waiting for the response, and then updating the variable with the
new value.
A Complete Program
------------------
......@@ -112,15 +149,16 @@ Here is what the complete program for our example looks like::
program level_check
float v;
assign v to "Input_voltage";
monitor v;
float voltage;
assign voltage to "Input_voltage";
monitor voltage;
short light;
assign light to "Indicator_light";
ss volt_check {
state light_off {
when (v > 5.0) {
when (voltage > 5.0) {
/* turn light on */
light = TRUE;
pvPut(light);
......@@ -128,7 +166,7 @@ Here is what the complete program for our example looks like::
}
state light_on {
when (v < 5.0) {
when (voltage < 5.0) {
/* turn light off */
light = FALSE;
pvPut(light);
......@@ -136,15 +174,12 @@ Here is what the complete program for our example looks like::
}
}
Each program must start with the word "program", followed by the name
Each program must start with the word :token:`program`, followed by the name
of the program (an identifier)::
program level_check
After that come declarations and then the state sets. Each state set
must have name, which comes after the word "ss"::
ss volt_check { ... }
After that come global declarations and then one or more state sets.
Adding a Second State Set
......@@ -200,17 +235,15 @@ parameter to :c:func:`delay` specifies the number of seconds, and must
be a floating point value (constant or expression).
At this point, you may wish to try an example with the two state sets.
You can jump ahead and read parts of Chapters 3-5. You probably want
to pick unique names for your process variables, rather than the ones
used above. You may also wish to replace the :c:func:`pvPut`
statements with ``printf`` statements to display "High" and "Low" on
your console.
You can jump ahead and read parts of the Chapters :doc:`Compiling` and
:doc:`Using` to find out how. You probably want to pick unique names
for your process variables, rather than the ones used above.
Variable Initialization and Entry Blocks
----------------------------------------
Since version 2.1 is has become simpler to initialize variables: you
Since version 2.1 it has become simpler to initialize variables: you
can use the same syntax as in C, i.e. initialize together with the
declaration::
......@@ -233,7 +266,7 @@ More complicated initialization can also be done using an
}
The actions in an entry block in a state declaration are executed
whenever the state is enetered. The state option ::
whenever the state is entered. The state option ::
option -e;
......@@ -246,35 +279,48 @@ that appears inside the entry block is executed only once when the
state is entered for the first time.
Variable Names Using Macros
---------------------------
PV Names Using Program Parameters
---------------------------------
You can specify the names of process variables or parts of them at
run-time. This is done by using macro substitution. In our example we
could replace the :token:`assign` statements with the following::
You can use program parameter substitution to parameterize the PV names
in your program. In our example we could replace the :token:`assign`
statements with the following::
assign v to "{unit}:ai1";
assign voltage to "{unit}:ai1";
assign vout to "{unit}:ao1";
The string within the curly brackets is a macro which has a name
("unit" in this case). At run-time you give the macro a value,
which is substituted in the above string to form a complete process
variable name. For example, if the macro "unit" is given
a name "DTL_6:CM_2", then the run-time variable name is
"DTL_6:CM_2:ai1". More than one macro may be specified within
a string, and the entire string may be a macro. See
:ref:`run time parameters` for more on macros.
The string within the curly brackets is the name of a program parameter
and the whole thing (the name and the brackets) are replaced with the
value of the parameter. For example, if the parameter "unit" has value
"DTL_6:CM_2", then the expanded PV name is "DTL_6:CM_2:ai1". See
:ref:`run time parameters` for more on program parameters (and
particularly how to give them values).
Data Types
----------
The allowable variable declaration types correspond to the C types:
``char``, ``unsigned char``, ``short``, ``unsigned short``, ``int``,
``unsigned int``, ``long``, ``unsigned long``, ``float`` , and
``double``. In addition there is the type ``string``, which is a fixed
array size of type ``char`` (at the time of writing, a string can hold
40 characters). Sequencer variables having any of these types may be
In earlier versions, variables were restricted to a hand full of
predefined types, plus one or two-dimensional arrays of these.
This is no longer true: you can declare variables of any type you like.
The only restrictions are:
#. you cannot *define* new types, only use them in declarations
#. when using type aliases ("typedef") you must prefix them with
the keyword "typename"
#. only variables of the above mentioned restricted list can be
:token:`assign`\'ed to PVs.
The built-in types are: ``char``, ``unsigned char``, ``short``,
``unsigned short``, ``int``, ``unsigned int``, ``long``, ``unsigned
long``, ``float`` , and ``double``. These correspond exactly to their C
equivalents. In addition there is the type ``string``, which is an array
of 40 ``char``.
Sequencer variables having any of these types may be
assigned to a process variable. The type declared does not have to be
the same as the native control system value type. The conversion
between types is performed at run-time. For more details see the
......@@ -481,13 +527,13 @@ monitors posted on the channel. Often this doesn't matter, but
sometimes it does. For example, a variable may transition to 1 and
then back to 0 to indicate that a command is active and has completed.
These transitions may occur in rapid succession. This problem can be
avoided by using the :token:`syncQ` statement to associate a variable
with both a queue and an event flag. The :c:func:`pvGetQ` function
retrieves and removes the head of queue.
avoided by using the :token:`syncq` statement to associate a variable
with a queue. The :c:func:`pvGetQ` function retrieves and removes the
head of queue.
This example illustrates a typical use of :c:func:`pvGetQ` : setting a
command variable to 1 and then changing state as an active flag
transitions to 1 and then back to 0. Note the use of :c:func:`pvFreeQ`
transitions to 1 and then back to 0. Note the use of :c:func:`pvFlushQ`
to clear the queue before sending the command. Note also that, if
:c:func:`pvGetQ` hadn't been used then the active flag's transitions
from 0 to 1 and back to 0 might both have occurred before the
......@@ -496,26 +542,25 @@ from 0 to 1 and back to 0 might both have occurred before the
long command; assign command to "commandVar";
long active; assign active to "activeVar"; monitor active;
evflag activeFlag; syncQ active activeFlag;
syncq active 2;
ss queue {
state start {
when () {
pvFreeQ( active );
entry {
pvFlushQ( active );
command = 1;
pvPut( command );
} state sent
}
state sent {
}
when ( pvGetQ( active ) && active ) {
} state high
}
state high {
when ( pvGetQ( active ) && !active ) {
} state done
}
state done {
/* ... */
}
}
The ``active`` SNL variable could have been an array in the above
......@@ -524,6 +569,8 @@ related control system ``active`` flags. In this case, the queue
would have had an entry added to it whenever a monitor was posted
on any of the underlying control system ``active`` flags.
.. todo:: check if the implementation conforms to the above
Asynchronous Use of pvGet
-------------------------
......@@ -556,7 +603,7 @@ affect put operations, :c:func:`pvPut`, like :c:func:`pvGet`, accepts
an optional ``SYNC`` or ``ASYNC`` argument, which forces a synchronous
or asynchronous put. For example::
pvPut( init[i], SYNC );
pvPut(init[i], SYNC);
:c:func:`pvPutComplete` supports arrays and can be used to check
whether a set of puts have all completed. This example illustrates how
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment