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 |
unit | no | no | string | Informative only - Unit (for steps per unit), e.g. mm, deg, rad, etc. |
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 | yes | 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 |
check_discrepancy | no | no | bool | Define if discrepancy must be checked before a movement ; defaults to True |
encoder | no | no | string | Name of an existing Encoder object linked with this axis |
closed loop | no | - | dict | Axis subsection to configure closed loop (see closed loop) |
display_digits | no | no | int | Number of digits used to print positions of this axis |
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 of the apply_config()
method of Axis
objects.
apply_config()
has a reload
parameter which is False
by default.
This parameter forces the reload of the YAML configuration files, otherwise the configuration to apply is the one that was previously loaded.
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.
It is possible to selectively apply only some parameters, using keyword arguments. The possible arguments are:
- acceleration
- velocity
- limits
- sign
- backlash
The default value is True
, which means the parameter is applied from
the last known configuration (or from the freshly read one if reload
is set to True
).
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 an Icepap:
DEMO [2]: bcumot2
Out [2]: AXIS bcumot2
position(deg) dial(deg) offset sign steps_per_unit tolerance
45.9300 45.9300 0.0000 1 200.00 0.0001
CURRENT VALUE | CONFIG VALUE
------------- | ------------
limits [low ; high] [ -inf ; inf] | [ -inf ; inf]
velocity 10.0 | 10.0
velocity limits [low ; high] [ inf ; inf] | [ inf ; inf]
acceleration 10.0 | 10.0
acctime 1.0 | 1.0
backlash 0.0000 | 0.0000
STATE(s): HOME OFF ALARM ALARMDESC
ICEPAP AXIS:
address: 2
axis status: POWER: OFF CLOOP: ON WARNING: NONE
ALARM: Control encoder alarm
IcepapEncoders(ENCIN='9345', ABSENC='0', INPOS='64698538',
MOTOR='9139', AXIS='9186', SYNC='9186')
ENCODER:
steps per unit: 200.0
unit: None
tolerance (position check at end of a move): 0.01
dial_measured_position: 46.72500
CLOSED LOOP: None
MOTION HOOKS: None
CONTROLLER: name: bcumots (type bcumot2.controller for more information)
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 |
unit | R | string | Informative only - Unit (for steps per unit), e.g. mm, deg, rad, etc. |
velocity | R+W | float | Get or set the axis velocity in units.s-1 |
config_velocity | R | float | Return 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 | Return 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 | Return 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 |
dial_limits | R+W | (float or None, float or None) | Get or set limits in dial units |
config_limits | R | (float or None, float or None) | Return (low_limit, high_limit), from the 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+W | float | Get or set the backlash applied to the axis |
is_moving | R | bool | Return whether the axis is moving |
dial | R+W | float | Get or set the axis dial position |
offset | R+W | float | Get or set the current offset for user position calculation |
sign | R+W | int | Get or set 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 | Return 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 |
display_digits | R | int | Number of digits used to print positions of this axis |
state | R | AxisState | Return the state of the axis (MOVING, READY, ON_LIMIT, etc) |
encoder | R | Encoder[None] | Return the encoder object associated to this axis |
name¶
An unique name to identify the Axis
object
unit¶
Informative only - Unit (for steps per unit), e.g. mm, deg, rad, etc.
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 motor controller class 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. It is also possible to set or retrieve the offset value
with the .offset
property. The sign is read from the configuration.
Then the sign can be set or retrieved with the .sign
property.
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
>>> axis.offset = 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.
Note
In the configuration, limits must be specified 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 unitslow_limit
(R+W): Get or set the soft low limit in user unitshigh_limit
(R+W): Get or set the soft high limit in user unitsconfig_limits
(R): Return (low_limit, high_limit), from the configuration in user unitsdial_limits
(R+W): Get or set the (low_limit, high_limit) tuple in dial 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¶
Return whether the axis is moving
tolerance¶
Axis tolerance is the acceptable discrepancy between controller position and last known axis dial position when starting a move.
Default value is 1E-4
Note
This test is performed before a move except if check_discrepancy
is
set to False
in config or dynamically.
Note
Do not confuse with encoder’s tolerance parameter
display_digits¶
display_digits
is a configuration parameter used to define the number of digits
to print when a position concerning this axis is displayed. This can be the
position but also a limit or the target position of a move.
display_digits
must be an int in [0; N]
.
If display_digits
is invalid, the default number of digits to use is 5.
If display_digits
is not set, the default number of digits to use is
determined as follow:
If steps_per_unit is 1
Use tolerance
Else
Use steps_per_unit
Note for developpers
The formating of of the outputs is done via bliss.common.axis.axis_rounder() function
.
Example:
mm1 : disabled
mm2 : display_digits: 4
mm6 : steps_per_unit: 100000 -> 7 digits
mm11 : steps_per_unit: 1 -> use tolerance
tolerance: 0.01 -> 2 digits
mm12 : display_digits: 0
mm13 : display_digits: 1.2 -> invalid -> use 5
mm14 : display_digits: 15
DEMO [2]: wa()
Current Positions: user
dial
mm1[parsec] mm2 mm6 mm11 mm12 mm13 mm14
------------- ------- --------- ------ ------ ------- -----------------
*DIS* 10.1235 0.0000000 0.00 1 0.00000 0.000120000000000
*DIS* 10.1235 0.0000000 0.00 1 0.00000 0.000120000000000
encoder¶
Encoders are standalone objects which can be referred by axes. Thus they have a dedicated section here: 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 power is switched off’
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.state
Out [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.
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()¶
Save settings (velocity, acceleration, limits, sign, backlash) into the corresponding Beacon YML configuration file.
This is the opposite operation of apply_config()
.
Keyword arguments can be specified to selectively write only some parameters into configuration:
- velocity
- acceleration
- limits
- sign
- backlash
By default, those keyword arguments are all set to True
, which
means all settings are written to the configuration file.
on()¶
off()¶
get_info()¶
dial2user() user2dial()¶
The methods translate a given position between DIAL units and USER units.
It uses sign
and offset
properties exposed by the axis.
The actual transformation is the following one.
USER = sign * DIAL + offset
DIAL = (USER - offset) / sign
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
Warning
Jog motion is mainly used inside sequences. It can be hazardous to use it from a BLISS session shell as it operates in backgroud.
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:
" mot4: discrepancy between dial (0.123) and controller position (0.100)
diff=1.06 tolerance=0.0001 => aborting movement."
Note
If check_discrepancy
is set to False
(in config or dynamically), this test is ignored.
ex:
# motor mot4 has been moved via IcepapCMS
DEMO [1]: umvr(mot4, 1)
!!! === RuntimeError: mot4: discrepancy between dial (3.0600) and controller position (2.0000)
diff=1.06 tolerance=0.0001 => aborting movement. === !!!
DEMO [4]: mot4.config.set("check_discrepancy", False)
DEMO [5]: umvr(mot4, 1)
Moving mot4 from 8.0600 to 9.0600
mot4
user 9.060
dial 4.060
After a movement, if:
- an encoder is associated to the axis
- AND
check_encoder
is set to True in config
then 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"