Skip to content

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:

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 [1]: samy
 Out [1]: axis name (R): samy
               state (R): READY (Axis is READY)
               unit (R): None
               offset (R): 0.0
               backlash (R): -0.00571429
               sign (R): 1
               steps_per_unit (R): 52500.0
               tolerance (R) (to check pos. before a move): 0.005
               motion_hooks (R): []
               dial (RW): 7.40000
               position (RW): 7.40000
               _hw_position (R): 7.40000
               hw_state (R): READY (Axis is READY)
               limits (RW):      (0.0, 15.8)  (config: (0.0, 15.8))
               acceleration (RW):    0.95238  (config:    0.95238)
               acctime (RW):         0.50000  (config:    0.50000)
               velocity (RW):        0.47619  (config:    0.47619)
          ICEPAP:
               host: iceid212 (ID: 0008.01A1.49C6) (VER: 3.17)
               address: 42
               status: POWER: ON    CLOOP: ON    WARNING: NONE    ALARM: NO
          ENCODER:
               tolerance (to check pos at end of move): 0.001
               encoder value: 388498
               dial_measured_position:    7.39998

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:

Axis initialization logic

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 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

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
or

>>> 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 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): Return (low_limit, high_limit), from the configuration in user units
  • dial_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:

changing acceleration

backlash

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
  • .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:

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"

Move loop

Move procedure