How to write a trigger/gate controller
The basics
An example of a hypothetical Springfield trigger/gate controller will be build incrementally from scratch to aid in the explanation.
By now you should have read the general controller basics chapter. You should be able to create a TriggerGateController with:
a proper constructor
add and delete axis methods
get axis state
import springfieldlib
from sardana.pool.controller import TriggerGateController
class SpringfieldTriggerGateController(TriggerGateController):
def __init__(self, inst, props, *args, **kwargs):
super(SpringfieldTriggerGateController, self).__init__(inst, props, *args, **kwargs)
# initialize hardware communication
self.springfield = springfieldlib.SpringfieldTriggerHW()
# do some initialization
self._triggers = {}
def AddDevice(self, axis):
self._triggers[axis] = True
def DeleteDevice(self, axis):
del self._triggers[axis]
StateMap = {
1 : State.On,
2 : State.Moving,
3 : State.Fault,
}
def StateOne(self, axis):
springfield = self.springfield
state = self.StateMap[ springfield.getState(axis) ]
status = springfield.getStatus(axis)
return state, status
The examples use a springfieldlib
module which emulates a trigger/gate
hardware access library.
The springfieldlib
can be downloaded from
here
.
The Springfield trigger/gate controller can be downloaded from
here
.
The following code describes a minimal Springfield base trigger/gate controller which is able to return the state of an individual trigger as well as to start a synchronization:
class SpringfieldBaseTriggerGateController(TriggerGateController):
"""The most basic controller intended from demonstration purposes only.
This is the absolute minimum you have to implement to set a proper trigger
controller able to get a trigger value, get a trigger state and do an
acquisition.
This example is so basic that it is not even directly described in the
documentation"""
def __init__(self, inst, props, *args, **kwargs):
"""Constructor"""
super(SpringfieldBaseTriggerGateController, self).__init__(
inst, props, *args, **kwargs)
self.springfield = springfieldlib.SpringfieldTriggerHW()
def StateOne(self, axis):
"""Get the specified trigger state"""
springfield = self.springfield
state = springfield.getState(axis)
if state == 1:
return State.On, "Trigger is stopped"
elif state == 2:
return State.Moving, "Trigger is running"
elif state == 3:
return State.Fault, "Trigger has an error"
def StartOne(self, axis, value=None):
"""acquire the specified trigger"""
self.springfield.StartChannel(axis)
def SynchOne(self, axis, synchronization):
self.springfield.SynchChannel(axis, synchronization)
def StopOne(self, axis):
"""Stop the specified trigger"""
self.springfield.stop(axis)
Get trigger state
To get the state of a trigger, sardana calls the
StateOne()
method. This method
receives an axis as parameter and should return either:
The state should be a member of State
(For backward
compatibility reasons, it is also supported to return one of
PyTango.DevState
). The status could be any string.
Prepare for measurement
To prepare a trigger for a measurement you can use the
PrepareOne()
method which
receives as an argument the number of starts of the whole measurement.
This information may be used to prepare the hardware for generating
multiple events (triggers or gates) in a complex measurement
e.g. Deterministic scans.
Load synchronization description
To load a trigger with the synchronization description
sardana calls the SynchOne()
method.
This method receives axis and synchronization parameters.
Here is an example of the possible implementation of
SynchOne()
:
class SpringfieldTriggerGateController(TriggerGateController):
def SynchOne(self, axis, synchronization):
self.springfield.SynchChannel(axis, synchronization)
Synchronization description
Synchronization is a data structure following a special convention. It is composed from the groups of equidistant intervals described by: the initial point and delay, total and active intervals and the number of repetitions. These information can be expressed in different synchronization domains if necessary: time and/or position.
Sardana defines two enumeration classes to help in manipulations of the
synchronization description. The SynchParam
defines the
parameters used to describe a group. The SynchDomain
defines the possible domains in which a parameter may be expressed.
The following code demonstrates creation of a synchronization description expressed in time and position domains (moveable’s velocity = 10 units/second and acceleration time = 0.1 second). It will generate 10 synchronization pulses of length 0.1 second equally spaced on a distance of 100 units.
from sardana.pool import SynchParam, SynchDomain
synchronization = [
{
SynchParam.Delay: {SynchDomain.Time: 0.1, SynchDomain.Position: 0.5},
SynchParam.Initial: {SynchDomain.Time: None, SynchDomain.Position: 0},
SynchParam.Active: {SynchDomain.Time: 0.1, SynchDomain.Position: 1},
SynchParam.Total: {SynchDomain.Time: 1, SynchDomain.Position: 10},
SynchParam.Repeats: 10,
}
]
Important
Synchronization description value in position domain is in dial position
since version 3.3.3. The possibility of using it with user position was
maintained as backwards compatibility but is deprecated. Backwards compatibility
is only available if you don’t use the moveable_on_input
trigger/gate
element configuration.
Start a trigger
When an order comes for sardana to start a trigger, sardana will call the
StartOne()
method. This method receives
an axis as parameter. The controller code should trigger the hardware acquisition.
Here is an example of the possible implementation of
StartOne()
:
class SpringfieldTriggerGateController(TriggerGateController):
def StartOne(self, axis):
self.springfield.StartChannel(axis)
As soon as StartOne()
is invoked,
sardana expects the trigger to be running. It enters a high frequency
synchronization loop which asks for the trigger state through calls to
StateOne()
. It will keep the loop
running as long as the controller responds with State.Moving
.
If StateOne()
raises an exception
or returns something other than State.Moving
, sardana will assume the trigger
is stopped and exit the synchronization loop.
For an synchronization to work properly, it is therefore, very important that
StateOne()
responds correctly.
Stop a trigger
It is possible to stop a trigger when it is running. When sardana is ordered to
stop a trigger synchronization, it invokes the
StopOne()
method. This method receives
an axis parameter. The controller should make sure the desired trigger is
gracefully stopped.
Here is an example of the possible implementation of
StopOne()
:
class SpringfieldTriggerGateController(TriggerGateController):
def StopOne(self, axis):
self.springfield.StopChannel(axis)
Abort a trigger
In an emergency situation, it is desirable to abort a synchronization
as fast as possible. When sardana is ordered to abort a trigger synchronization,
it invokes the AbortOne()
method. This method receives an axis parameter. The controller should make
sure the desired trigger is stopped as fast as it can be done.
Here is an example of the possible implementation of
AbortOne()
:
class SpringfieldTriggerGateController(TriggerGateController):
def AbortOne(self, axis):
self.springfield.AbortChannel(axis)
Advanced topics
Coupled and multiplexor modes in position domain
Trigger/gate controller can work in either of two modes:
coupled - one input is coupled to one output
multiplexor - many inputs may produce synchronization signals on many outputs
See more details in Trigger/Gate API reference.
If hardware can work in both modes, then a dedicated axis number should be used to identify the outputs e.g. IcePAPTriggerGateController reserve axes 1, 2, 3, etc. for the coupled mode outputs and axis 0 for the multiplexor mode output.
In case of supporting the mutliplexor mode the trigger/gate controller plugin
must implement the axis parameter, called active_input
(settable with SetAxisPar()
).
Sardana will determine its value based on the moveable_on_input
trigger/gate element
configuration and the measurement group’s moveable
attribute.
Trigger/gate element may be requested to perform synchronization in
Time
domain only
e.g. during the ct
or timescan
.
In this case the active_input
will be set to a special value None
indicating no input should be used.
The active_input
parameter is set during the measurement preparation phase
prior to setting the synchronization description.
Here is an example of a possible implementation of SetAxisPar()
:
class SpringfieldTriggerGateController(TriggerGateController):
def SetAxisPar(self, axis, name, value):
if name == "active_input":
if value is None:
self.springfield.DeactivateInput(axis)
else:
self.springfield.SetActiveInput(axis, value)