Device control

Parameters

In Concert, a device is a software abstraction for a piece of hardware that can be controlled. Each device consists of a set of named Parameter instances and device-specific methods. Devices rely heavily on concurrent execution, so that multiple devices can do multiple actions at the same time. In order for the devices to be able to use concurrency already in their constructors, they must be instantiated with the await keyword like this:

from concert.devices.motors.dummy import LinearMotor
motor = await LinearMotor()

The devices contain several parameters and if you know the parameter name, you can get a reference to the parameter object by using the index operator:

pos_parameter = motor['position']

To set and get parameters explicitly , you can use the Parameter.get() and Parameter.set() coroutine methods:

await pos_parameter.set(1 * q.mm)
print (await pos_parameter.get())

Both methods return a coroutine (see Concurrent execution for details) and give the control back to you, so that other things can (and should) happen concurrently. As you can see, to get the result of a coroutine you use the await keyword.

An easier way to set and get parameter values are properties via the dot-name-notation:

motor.position = 1 * q.mm
print (motor.position)

As you can see, accessing parameters this way will always be synchronous and block execution until the value is set or fetched. If you press ctrl-c while you are setting a parameter the function will stop and a cancelling action will be called, like stopping a motor, so that you don’t accidentaly crush your devices. However, please be aware that this is up to device implementation, so you should check if the device you are using is safe in this manner.

Parameter objects are not only used to communicate with a device but also carry meta data information about the parameter. The most important ones are Parameter.name, Quantity.unit (in case of a parameter having a physical unit, like motor’s position) and the doc string describing the parameter. Moreover, parameters can be queried for access rights using Parameter.writable.

To get all parameters of an object, you can iterate over the device itself

for param in motor:
    print("{0} => {1}".format(param.unit if hasattr(param,'unit') else None, param.name))

Saving state

In some scenarios you would like to come back to a certain state. Let’s suppose, you have a motor that you want to check if it moves. If it does, you want it to go back to the same place it came from. For these cases you can use Device.stash() to store the current state of a device and Device.restore() to go back. Because this is done in a stacked fashion, you can, for example, model local coordinate pretty easily:

await motor.stash()

# Do movements aka modify the "local" coordinate system
await motor.move(1 * q.mm)

# Go back to the original state
await motor.restore()

Locking parameters

In case you want to prevent a parameter from being written you can use ParameterValue.lock(). If you specify a permanent parameter to be True the parameter cannot be unlocked anymore. In case you want to unlock a parameter you can use ParameterValue.unlock(), to get the state you can check the attribute ParameterValue.locked. All the parameters within a device can be locked and unlocked at once, for example one can do:

motor['position'].lock()
motor.position = 10 * q.mm
# Does not work, you will get a LockError
motor['position'].locked
True

motor['position'].unlock()

# Works as expected
motor.position = 10 * q.mm

# Lock the whole device (all parameters)
motor.lock(permanent=True)

# This will not work anymore
motor.unlock()
# You will get a LockError

Limits

Limits allow you to restrict setting Quantity to a certain range. You can specify the Quantity.lower and Quantity.upper limits. Usage:

# Blocking version
motor['position'].lower = -10 * q.mm
print(motor['position'].lower)
# Coroutine version
await motor['position'].set_lower(-15 * q.mm)
print(await motor['position'].get_lower())

# Blocking version
motor['position'].upper = 10 * q.mm
print(motor['position'].upper)
# Coroutine version
await motor['position'].set_upper(15 * q.mm)
print(await motor['position'].get_upper())

# Locking
motor['position'].lock_limits()
# Will not work, you will get a LockError
motor['position'].lower = -10 * q.mm

motor['position'].unlock_limits()
# This will work again
motor['position'].lower = -10 * q.mm

motor['position'].lock_limits(permanent=True)
# This will not work anymore until you restart the session
motor['position'].unlock_limits()

Emergency stop

On ctrl-k, the background tasks are cancelled and on top of that on all devices Device.emergency_stop() will be called in order to bring them to a standstill.