Using Devices

The station exposes devices, each of which is a remote software object that manages part of the station. Each device has the following properties:

  • It has a state,

  • Many devices manage and represent hardware in the station,

  • It exposes read-only attributes, that expose values from within the device or from the hardware it represents,

  • It exposes read-write attributes, that allow controlling the functionality of the device, or the hardware it represents,

  • It exposes properties, which are fixed configuration parameters (such as port numbers and timeouts),

  • It exposes commands, that request the execution of a procedure in the device or in the hardware it manages.

The devices are accessed remotely using DeviceProxy objects. See Monitoring & Control on how to do this.

States

The state of a device is then queried with device.state(). Each device can be in one of the following states:

  • DevState.OFF: The device is not operating,

  • DevState.INIT: The device is being initialised,

  • DevState.STANDBY: The device is initialised and ready to be configured further,

  • DevState.ON: The device is operational,

  • DevState.ALARM: The device is operational, but one or more attributes are in alarm,

  • DevState.FAULT: The device is malfunctioning. Functionality cannot be counted on,

  • DevState.DISABLE: The device is not operating because its hardware has been shut down.

  • The device.state() function can throw an error, if the device cannot be reached at all. For example, because it’s docker container is not running. See the Docker device on how to start it.

digraph finite_state_machine { fontname="Helvetica,Arial,sans-serif" node [fontname="Helvetica,Arial,sans-serif"] edge [fontname="Helvetica,Arial,sans-serif"] rankdir=LR; node [shape = doublecircle fixedsize=true width=0.7]; off; node [shape = circle fixedsize=true width=0.9]; init -> off [label = "user", color="red"]; standby -> off [label = "user", color="red"]; on -> off [label = "user", color="red"]; alarm -> off [label = "user", color="red"]; off -> init [label = "device", color="green"]; init -> standby [label = "device", color="green"]; standby -> on [label = "device", color="green"]; on -> alarm [label = "device", color="green"]; init -> fault [label = "device", color="green"]; standby -> fault [label = "device", color="green"]; on -> fault [label = "device", color="green"]; alarm -> fault [label = "device", color="green"]; fault -> init [label = "user", color="red"]; fault -> off [label = "user", color="red"]; standby -> disable [label = "user", color="green"]; on -> disable [label = "user", color="green"]; alarm -> disable [label = "user", color="green"]; disable -> off [label= "user", color="red"]; }

Each device provides the following commands to change the state:

boot():

Turn on the device, and initialise the hardware. Moves from OFF to ON.

warm_boot():

Turn on the device, but do not change the hardware. Moves from OFF to ON.

disable_hardware():

Shut down the hardware related to the device. Moves from STANDBY, ON or ALARM to DISABLE

off():

Turn the device OFF from any state.

The following procedure is a good way to bring a device to ON from any state:

def force_start(device):
    if device.state() == DevState.FAULT:
        device.off()
    if device.state() == DevState.OFF:
        device.boot()

    return device.state()

Hint

If a command gives you a timeout, the command will still be running until it finishes. You just won’t know when it does or its result. In order to increase the timeout, use device.set_timeout_millis(timeout * 1000).

FAULT

If a device enters the FAULT state, it means an error occurred that is fundamental to the operation of the software device. For example, the connection to the hardware was lost. To see the error reason, use

status():

The verbose status of the device, f.e. the reason why the device went to FAULT.

Interaction with the device in the FAULT state is undefined, and attributes cannot be read or written. The device needs to be reinitialised, which typically involves the following sequence of commands:

# turn the device off completely first.
device.off()

# turn on the device and fully reinitialise it
# alternatively, device.warm_boot() can be used,
# in which case no hardware is reinitialised.
device.boot()

Of course, the device could go into FAULT again, even during the boot() command, for example because the hardware it manages is unreachable. To debug the fault condition, check the Logs of the device in question.

Initialise hardware

Most devices provide the following commands, in order to configure the hardware with base settings. Note that these are automatically called during boot(), in this order:

initialise():

Initialise the device (connect to the hardware). Moves from OFF to STANDBY.

power_hardware_on():

For devices that control hardware, this command turns on power to it.

power_hardware_off():

For devices that control hardware, this command turns off power to it.

set_defaults():

Upload default attribute settings from the TangoDB to the hardware.

on():

Mark the device as operational. Moves from STANDBY to ON.

Attributes

The device can be operated in ON state, where it exposes attributes and commands. The attributes can be accessed as python properties, for example:

recvh = DeviceProxy("STAT/RECVH/1")

# turn on all LED0s
recvh.RCU_LED0_RW = [True] * 32

# retrieve the status of all LED0s
print(recvh.RCU_LED0_R)

The attributes with an:

  • _R suffix are monitoring points, reflecting the state of the hardware, and are thus read-only.

  • _RW suffix are control points, reflecting the desired state of the hardware. They are read-write, where writing requests the hardware to set the specified value. Reading them returns the last requested value.

Meta data

A description of the attribute can be retrieved using:

print(recvh.get_attribute_config("RCU_LED0_R").description)

Attribute masks

Several devices employ attribute masks in order to toggle which elements in their hardware array are actually to be controlled. This construct is necessary as most control points consist of arrays of values that cover all hardware elements. These array control points are always fully sent: it is not possible to update only a single element without uploading the rest. Without a mask, it is impossible to control a subset of the hardware.

The masks only affect writing to attributes. Reading attributes (monitoring points) always result in data for all elements in the array.

For example, the RCU_mask_RW array is the RCU mask in the recvh device. It behaves as follows, when we interact with the RCU_LED0_R(W) attributes:

recvh = DeviceProxy("STAT/RECVH/1")

# set mask to control all RCUs
recvh.RCU_mask_RW = [True] * 32

# request to turn off LED0 for all RCUs
recvh.RCU_LED0_RW = [False] * 32

# <--- all LED0s are now off
# recvh.RCU_LED0_R should show this,
# if you have the RCU hardware installed.

# set mask to only control RCU 3
mask = [False] * 32
mask[3] = True
recvh.RCU_mask_RW = mask

# request to turn on LED0, for all RCUs
# due to the mask, only LED0 on RCU 3
# will be set.
recvh.RCU_LED0_RW = [True] * 32

# <--- only LED0 on RCU3 is now on
# recvh.RCU_LED0_R should show this,
# if you have the RCU hardware installed.