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 :doc:`../interfaces/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 :ref:`docker` device on how to start it. .. graphviz:: 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 :doc:`../interfaces/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: 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: 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.