The BLISS Axis object¶
In most cases a BLISS Axis represents a motor driven by a physical motor controller.
This page presents the detailed usage of a BLISS axis.
Other more or less related pages:
- Short presentation of Bliss Axis usage in BLISS shell
- BLISS Shell standard functions to drive motors
- How to write a new class to support a motor controller in BLISS
- How to write a Calculational Motor Controller
- Usual scans
- Shutters
Configuration¶
The Axis
objects need to be declared along their controller
in the BLISS configuration with an unique name, and a set of
configuration parameters.
Note
See the motor controller configuration templates to learn about how to configure axes. For example, see Icepap configuration to declare Icepap motor controller axes.
Default configuration parameters¶
Configuration parameters from Beacon YAML files are passed to
the Axis
constructor.
Parameter name | Required | Setting? | Type | Description |
---|---|---|---|---|
name | yes | no | string | An unique name to identify the Axis object |
steps_per_unit | yes | no | float | Number of steps to send to the controller to make a move of 1 unit (eg. 1 mm, 1 rad) |
velocity | yes | yes | float | Nominal axis velocity in units.s-1 |
acceleration | yes | yes | float | Nominal acceleration value in units.s-2 |
sign | no | no | int | Accepted values: 1 or -1. User position = (sign * dial_position) + offset ; defaults to 1 |
low_limit | no | yes | float | Dial Lower limit for a move (None or not specified means: unlimited) ; defaults to unlimited |
high_limit | no | yes | float | Dial Higher limit for a move (None or not specified means: unlimited) ; defaults to unlimited |
backlash | no | no | float | Axis backlash in user units ; defaults to 0 |
tolerance | no | no | float | Accepted discrepancy between controller position and last known axis dial position when starting a move ; defaults to 1E-4 |
encoder | no | no | string | Name of an existing Encoder object linked with this axis |
unit | no | no | string | Informative only - Unit (for steps per unit), e.g. mm, deg, rad, etc. |
Note
Motor controllers with extra features may require more parameters. See the documentation of individual motor controllers to known about specific parameters.
Some configuration parameters are translated into settings, which means the corresponding value is also stored in redis (see Settings documentation).
Applying configuration changes¶
A change in YML configuration can be applied with use apply_config()
method of Axis
objects.
apply_config()
has got a reload
parameter which is False
by default.
This parameter forces the reload on YAML from file, otherwise, the configuration to apply is the one in memory (in redis ???).
Example: after changing velocity of ssu motor in YML file: ssu.apply_config(reload=False) # <— will apply old configuration ssu.apply_config(reload=True) # <— will apply new configuration
ssu.apply_config(reload=False)
is a common way to reset parameters
after changes of settings in a session for example.
see beacon db for usage
examples of apply_config()
.
Axis in BLISS shell¶
The typing helper allowing to print info in BLISS shell will print the following info by default for all ‘Axis’ objects:
example for a VSCANNER:
DEMO [1]: sampy Out [1]: axis name: sampy state: READY (Axis is READY) unit: None offset: 0.0 backlash: 0.0 sign: 1 steps_per_unit: 0.1 tolerance: 0.01 encoder: None motion_hooks: [] dial: 2.00001 position: 2.00001 _hw_position: 2.00001 hw_state: READY (Axis is READY) velocity: 10.0 (config: 10.0) acceleration: None limits: (0.0, 100.0) (config: (0.0, 100.0)) controller: <bliss.controllers.motors.vscanner.VSCANNER object at 0x7fe0bf0c2fd0> ...
More info are then displayed, taken from get_info()
method of the specific
controller.
example for a VSCANNER:
... ############################### Config: url=rfc2217://lid213.esrf.fr:28206 class=VSCANNER channel letter:X ############################### ?ERR: b'OK\r' ############################### '?INFO' command: firmware version : VSCANNER 01.02 output voltage : 0.200001 0.500205 unit state : READY ############################### $ Max. number of lines: 3276 Internal time step (microsec.): 50 Current settings: LINE -0.100193 0 1 C SCAN 0 0 1 U VEL 0.001 0 LTRIG MASK PTRIG MASK PIXEL 0 0 HDELAY 0 $ ###############################
Custom axis classes¶
Axis
is the default class that corresponds to controller motors,
however it is possible to specify a derived class with extra features
if needed. Some controllers may return instances of a special class
(for example, the IcePAP controller has a special class for linked axes).
User can also force a particular class to be instanciated, by adding
a class
item within the YAML configuration for the axis.
ModuloAxis¶
An Axis
whose positions are always between 0 and a modulo value set in the
YAML configuration (modulo
parameter). For example, a motor for a rotation
can be configured with class: ModuloAxis
and modulo: 360
.
NoSettingsAxis¶
An Axis
which does not store settings in redis – will always refer to hardware.
Implementation details¶
This chapter concerns who want to understand the internal mechanisms of BLISS Axis object.
bliss/common/
axis.py
motor_config.py
motor_group.py
motor_settings.py
scans.py
bliss/controllers/motor.py
Initialization¶
Motor controllers follow the following sequence to initialize Axis
objects:
When the Axis
object is initialized, settings have prevalence over static
configuration parameters, i.e. the axis velocity will be changed to the
one in redis, not to the nominal value from the configuration.
Note
Axis initialization does not happen when the motor controller is
loaded, or when an Axis
object is retrieved from Beacon. Indeed
Axis
objects implement the lazy initialization pattern: the
initialization sequence described above only happens the first
time the object is accessed.
Axis properties¶
Axis properties are built on top of the Python language properties,
which provide an elegant way to implement “getters and setters”
mechanisms. Assigning a value to a property sets the value, i.e. an
action may be triggered by the object when the descriptor gets
written. In the case of the Axis
object, it can trigger a
communication with the motor controller to set the velocity for
example.
It is enough to call the property to read the property value. Depending on the property, this can also trigger an action on the motor controller.
Property name | R/W? | Type | Description |
---|---|---|---|
name | R | string | Axis name |
velocity | R+W | float | Get or set the axis velocity in units.s-1 |
config_velocity | R | float | Returns the nominal velocity value from the configuration |
acceleration | R+W | float | Get or set the axis acceleration in units.s-2 |
config_acceleration | R | float | Returns the nominal acceleration value from the configuration |
acctime | R+W | float | Get or set the acceleration time; note: depends on both velocity and acceleration ; acctime = velocity / acceleration |
config_acctime | R | float | Returns the acceleration time taking into account nominal values for velocity and acceleration |
low_limit | R+W | float or None | Get or set the soft low limit in user units |
high_limit | R+W | float or None | Get or set the soft high limit in user units |
limits | R+W | (float or None, float or None) | Get or set soft limits in user units |
config_limits | R | (float or None, float or None) | Returns (low_limit, high_limit), from the in-memory configuration in user units |
steps_per_unit | R | float | Number of steps to send to the controller to make a move of 1 unit (eg. 1 mm, 1 rad) |
backlash | R | float | Returns the backlash applied to the axis |
is_moving | R | bool | Returns whether the axis is moving |
dial | R+W | float | Get or set the axis dial position |
offset | R | float | Returns the current offset for user position calculation |
sign | R | int | Returns the sign for user position calculation |
position | R+W | float | Get or set the axis user position ; User position = (sign * dial_position) + offset |
_hw_position | R | float | Returns the controller position for the axis ; forces a read on the controller |
_set_position | R+W | float | Last set position for the axis (target of last move, or current position) |
tolerance | R | float | Accepted discrepancy between controller position and last known axis dial position when starting a move ; defaults to 1E-4 |
state | R | AxisState | Returns the state of the axis (MOVING, READY, ON_LIMIT, etc) |
encoder | R | Encoder[None] | Returns the encoder object associated to this axis |
position¶
position
sign
user
dial
offset
steps_per_unit
Note
About units management
- On the user point of view, axes are moved in user units, whatever unit is used in the controller API
- user unit can be millimeter, micron, degree etc.
- controller unit is often steps or micron
- On the programmer point of view, the BLISS plugin is dealing with controller units (steps, microns, …)
- The programmer should not have to deal with units conversions.
Axis
object keeps track of both a dial and a user position.
The dial position is meant to agree with the readout of the physical
dial on the hardware stage. The value and the sign of the
.steps_per_unit parameter should be chosen so that the dial position
and its direction agree with the physical dial reading. Assigning a
value to the writable .dial
property sets the position on the motor
controller register.
The user position allows to use a logical reference frame, that does not interfere with the motor controller.
user_position = (sign * dial_position) + offset
Assigning a value to the .position
property sets the user
position. The offset is determined automatically, using the above
formula. The offset value can be retrieved with the .offset
property (read-only). The sign is read from the configuration. The
sign value can be retrieved with the .sign
property (read-only).
Changing the user position does not change anything on the motor controller. No communication with hardware is involved.
Resetting offset to 0 can be achieved with:
>>> axis.position = axis.dial >>> axis.offset 0.0
Position change events¶
Internally, the axis position is kept in a
Channel
object, which makes it is possible to
register a callback function to be called whenever the axis position
changes:
>>> from bliss.common import event >>> def example_callback(new_pos): print(f"I moved to {new_pos}") >>> event.connect(m0, "position", example_callback) >>> m0.rmove(1) I moved to 0.0 I moved to 0.241 I moved to 0.486 I moved to 0.691 I moved to 0.880 I moved to 1.0 >>>
Frequency: 20 ms ?
Note
The events can be used in particular for GUI
Note
The same applies for any setting or channel: dial, state, limits, velocity, acceleration
hardware_position¶
_hw_position
_set_position
m1._hw_position # just read (no cache), does not update settings m1._hw_position = 36 # —> INVALID m1._set_position = 36 # —> VALID
limits¶
A particular attention must be paid to the units of the limits.
In user interactions, limits are treated in USER units, but internaly and in the config, limits are managed in DIAL units.
Configuring limits in DIAL units in the config is relevant to avoid to impact
them with sign
and offset
.
Properties related to limits:
* limits
(R+W): Get or set soft limits in user units
* low_limit
(R+W): Get or set the soft low limit in user units
* high_limit
(R+W): Get or set the soft high limit in user units
* config_limits
(R): Returns (low_limit, high_limit), from the in-memory configuration in user units
Pushing the limits¶
Examples of limits behavior.
in config:
... - acceleration: 10 name: m4 steps_per_unit: 100 velocity: 10 high_limit: 90.0 low_limit: -90.0 ...
DEMO [2]: wm(m4) m4 ------- --------- User High 90.00000 Current 3.00000 Low -90.00000 Offset 0.00000 Dial High 90.00000 Current 3.00000 Low -90.00000
Setting the user position creates an offset. USER limits are impacted but not DIAL limits.
DEMO [3]: m4.position = 12 DEMO [4]: wm(m4) m4 ------- --------- User High 99.00000 Current 12.00000 Low -81.00000 Offset 9.00000 Dial High 90.00000 Current 3.00000 Low -90.00000
velocity¶
velocity
config_velocity
acceleration¶
acceleration
config_acceleration
acctime
config_acctime
Changing acceleration:
backlash¶
is_moving¶
tolerance¶
!!!
There is also a tolerance parameter for encoder, see:
encoder¶
Axis state¶
The .state
property returns the current state of an axis. The
returned value is an AxisState
instance, which holds a list of
states.
A description is associated to each state. The string representation
of the AxisState
object shows a human-readable description of the
state. Example here present the READY
state with its “Axis is
READY” description:
DEMO [1]: m0.state Out [1]: AxisState: READY (Axis is READY)
Pre-defined standard states are:
MOVING
, ‘Axis is moving’READY
, ‘Axis is ready to be moved’FAULT
, ‘Error from controller’LIMPOS
, ‘Hardware high limit active’LIMNEG
, ‘Hardware low limit active’HOME
, ‘Home signal active’OFF
, ‘Axis is disabled’
A motor can then have more than one state at once, states can be combined to represent complex situations. For example, a motor can be both ready to move and still touching a limit or being at home position.
DEMO [1]: m0.state Out [1]: AxisState: MOVING (Axis is moving) DEMO [2]: m1.state Out [2]: AxisState: READY (Axis is READY) | LIMPOS (Hardware high limit active)
A given state can be tested to know if a motor is in this state:
if m0.state.MOVING: print ("m0 motor is moving, please wait...")
Note
The in
operator of the Python language can be used to check
whether an axis is in a certain state.
TEST_SESSION [2]: 'READY' in m0.stateOut [2]: True
means:
"m0 has 'READY' state in it's current states list."
READY and MOVING are the states tested by the Axis engine to determine allowed actions on an axis object.
It is possible to define custom states (see how to write motor controllers).
Note
state
property is the list of current states, not the list
of all existing states.
To get the list of all existing states for a motor, use
states_list()
method:
DEMO [16]: m1.state.states_list() Out [16]: ['READY', 'MOVING', 'FAULT', 'LIMPOS', 'LIMNEG', 'HOME', 'OFF']
Caching¶
For performance considerations, the state of an axis is managed via a caching mechanism. There can be a discrepancy between real state of a motor and BLISS returned state.
To get a direct reading of the hardware, use: hw_state
property.
The reading hw_state
property does not update state
property.
It is possible to disable the caching mechanism in the config file ???
State change events¶
Similarly to the .position
property, it is possible to be notified
of state changes by registering to the state change event:
TEST_SESSION [12]: def state_change(new_state): ...: print(f"State changed to {str(new_state)}") TEST_SESSION [13]: event.connect(m0, "state", state_change) TEST_SESSION [14]: m0.rmove(1) State changed to MOVING (Axis is MOVING) State changed to MOVING (Axis is MOVING) State changed to READY (Axis is READY)
Axis commands¶
settings_to_config()¶
Saves settings (velocity
acceleration
limits
) into config (XML file or
beacon YML).
on()¶
off()¶
get_info()¶
dial2user() user2dial()¶
sync_hard() Synchronization with hardware¶
The Axis
object tries to minimize access to the physical motor
controller. In particular, it is assumed BLISS takes ownership of the
hardware devices, i.e. devices are not supposed to be driven
“externally”, by another software for example. Indeed, all Axis
settings are cached.
In some cases, though, another application (like icepapcms for the
IcePAP motor controller) can control a BLISS axis. Then any
further action on the Axis
object would end up with an exception
being raised, because of discrepancies between the axis cached state
and the hardware state.
In order to solve the problem, and to empty the internal cache, the
.sync_hard()
method can be called.
Moving stop() move() home() jog()¶
The Axis
object provides the following methods to start, monitor and
stop a movement:
.move(target_user_position, wait=True, relative=False)
- move to target position (absolute, except if relative=True)
.rmove(target_user_position, wait=True)
- do a relative move to target position
.home(switch=1, wait=True)
- do a home search
.jog(velocity, reset_position=None)
- start to move at constant speed ; velocity can be negative to indicate the opposite direction
- if reset position is set to 0, the controller position is set to 0 at the end of the jog move
- if reset position is a callable, it is called at the end of the jog move, passing the Axis object as first argument
.hw_limit(limit, wait=True)
- do a limit search
- a positive limit value means ‘limit + switch’, whereas a negative value means ‘limit - switch’
.wait_move()
- for motions started with
wait=False
, this allows to join with the end of the move
- for motions started with
.stop()
- send a stop command to the controller
- the move loop will exit
Before a movement, the position of the axis is read and compared to the BLISS Axis internal position. In case of difference outside the limit fixed by Axis tolerance configuration parameter, an exception is raised with message:
"discrepancy between dial (0.123) and controller position (0.100), aborting"
After a movement, if an encoder is associated to the axis, the encoder position is read and compared to the target position of the movement. In case of difference outside the limit fixed by Encoder tolerance, an exception is raised with message:
"didn't reach final position"