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.