Motion hooks¶
A Motion hook is a piece of code that can be attached to a motor or a list of motors and executed at particular moments.
Note
For brave old users of SPEC, motion hooks can replace cdef()
A new motion hook definition is a python class which inherits from the
base hook class bliss.common.hook.MotionHook
and implements the
bliss.common.hook.MotionHook.pre_move
and
bliss.common.hook.MotionHook.post_move
methods. The base
implementation of both methods does nothing so you may implement only
what you need.
Both methods receive a motion argument. It is a list of
bliss.common.axis.Motion
objects representing the current motion.
Motion hook objects also have an init()
method that can be overwritten:
it is called once, when the motion hook is activated for the first time.
Its goal is to allow some initialization before pre_move
and post_move
are called.
For example, it is allowed to iterate through the .axes
dictionary, and
to
Warning
Care has to be taken not to trigger a movement of a motor which
is being moved in one of the init()
, pre_move()
or post_move()
method. Doing so will most likely result in an infinite recursion error.
You can use pre_move()
to prevent a motion from occuring if a certain
condition is not satisfied. In this case pre_move()
should raise an
exception explaining the reason.
A hook is configured using the bliss YAML static configuration.
To link an axis with a specific hook you need to add a motion_hooks
key to your axis YAML configuration. It should be a list of
references to hooks defined somewhere else (see example below).
Example use case: motor with air-pad¶
Imagine that in your laboratory there is a motor m1
that move a heavy
granite table. Before it moves, an air-pad must be filled with air by
triggering a PLC and after the motion ends, the air-pad must be emptied.
Further, since there is no pressure meter, it has been determined
empirically that after the air-pad fill command is sent to the PLC, we
have to wait 1s for the pressure to reach a good value before moving and
wait 2s after the motion is finished.
So the hook implementation will look something like this:
# bliss/controllers/motors/airpad.py import gevent from bliss.common.hook import MotionHook class AirpadHook(MotionHook): """air-pad motion hook""" def __init__(self, name, config): self.config = config self.name = name self.plc = config['plc'] self.channel = config['channel'] super().__init__() def pre_move(self, motion_list): self.plc.set(self.channel, 1) gevent.sleep(1) def post_move(self, motion_list): self.plc.set(self.channel, 0) gevent.sleep(2)
Here is the corresponding YAML configuration:
# motors.yml plcs: - name: plc1 # here follows PLC configuration hooks: - name: airpad_hook plugin: bliss package: bliss.controllers.motors.airpad plc: $plc1 motors: - controller: Mockup plugin: emotion axes: - name: m1 # here follows motor configuration motion_hooks: - $airpad_hook
Note that in this example only one hook was used for the m1
motor. You
can define a list of hooks to be executed if you need. The hooks are
executed in the order given in the motion_hooks
list.
Example use case: preventing collisions¶
Hooks can be used to prevent a motion from occuring if certain conditions are not met.
Lets say that in your laboratory there are two detectors which can move in the XY plane and you want to prevent collisions between them.
det1 can only move in the Y axis using motor det1y
an det2
can
move in the X and Y axis using motors det2x
and det2y
.
Lets say that det1 is located at X1=10, Y1=200 when det1y=0
. For
collision purposes it is suficient to approximate the detector geometry
by a sphere of radius R1=5.
Lets say that det2 is located at X1=10, Y1=10 when det2x = 0
and det2y = 0
. For collision purposes it is suficient to approximate
the detector geometry by a sphere of radius R1=15.
So, every time that at least one of the three motors det1y
, det2x
or
det2y
moves, a pre-check needs to be made to be sure the motion is not
going to collide the two detectors.
The code should look something like this:
# bliss/controllers/motors/coldet.py import math import collections Point = collections.namedtuple('Point', 'x y') from bliss.common.hook import MotionHook class DetectorSafetyHook(MotionHook): """Equipment protection of pair of detectors""" D1_REF = Point(10, 200) D2_REF = Point(10, 10) SAFETY_DISTANCE = 5 + 15 class SafetyError(Exception): pass def __init__(self, name, config): self.axes_roles = {} super(DetectorSafetyHook, self).__init__() def init(self): # store which axis has which # roles in the system for axis in self.axes.values(): tags = axis.config.get('tags') if 'd1y' in tags: self.axes_roles[axis] = 'd1y' elif 'd2x' in tags: self.axes_roles[axis] = 'd2x' elif 'd2y' in tags: self.axes_roles[axis] = 'd2y' else: raise KeyError('detector motor needs a safety role') def pre_move(self, motion_list): # determine desired positions of all detector motors: # - if motor in this motion, get its target position # - otherwise, get its current position target_pos = dict([(axis, axis.position()) for axis in self.axes_roles]) for motion in motion_list: if motion.axis in target_pos: target_pos[motion.axis] = motion.target_pos # build target positions by detector motor role target_pos_role = dict([(self.axes_roles[axis], pos) for axis, pos in target_pos.items()]) # calculate where detectors will be in space d1 = Point(self.D1_REF.x, self.D1_REF.y + target_pos_role['d1y']) d2 = Point(self.D2_REF.x + target_pos_role['d2x'], self.D2_REF.y + target_pos_role['d2y']) # calculate distance between center of each detector distance = math.sqrt((d2.x - d1.x)**2 + (d2.y - d1.y)**2) if distance < self.SAFETY_DISTANCE: raise self.SafetyError('Cannot move: motion would result ' \ 'in detector collision')
Find below the corresponding YAML configuration:
hooks: - name: det_hook class: DetectorSafetyHook module: motors.coldet plugin: bliss controllers: - name: det1y acceleration: 10 velocity: 10 steps_per_unit: 1 low_limit: -1000 high_limit: 1000 tags: d1y unit: mm motion_hooks: - $det_hook - name: det2x acceleration: 10 velocity: 10 steps_per_unit: 1 low_limit: -1000 high_limit: 1000 tags: d2x unit: mm motion_hooks: - $det_hook - name: det2y acceleration: 10 velocity: 10 steps_per_unit: 1 low_limit: -1000 high_limit: 1000 tags: d2y unit: mm motion_hooks: - $det_hook
Note
For demonstration purposes, these examples are minimalistic and do no error checking for example. Feel free to use this code but please take this into account.