Hardware Objects

Creating a new hardware object is relatively straight forward. If creating a new type of object, first an abstract object needs to be created, then the controls system specific implementation.

For example consider create a motor (assuming one doesnt exist), first create the abstract object:

# daiquiri/core/hardware/abstract/motor.py
from marshmallow import fields

from daiquiri.core.hardware.abstract import HardwareObject
from daiquiri.core.schema.hardware import HardwareSchema
from daiquiri.core.schema.validators import RequireEmpty


MotorStates = ["READY", "MOVING", "FAULT", "UNKNOWN", "LOWLIMIT", "HIGHLIMIT"]


class MotorPropertiesSchema(HardwareSchema):
    position = fields.Float()
    ...
    unit = fields.Str(metadata={"readOnly": True})


class MotorCallablesSchema(HardwareSchema):
    move = fields.Float()
    stop = RequireEmpty()
    wait = RequireEmpty()


class Motor(HardwareObject):
    _type = "motor"
    _state_ok = [MotorStates[0], MotorStates[1]]

    _properties = MotorPropertiesSchema()
    _callables = MotorCallablesSchema()

An abstract object defines Properties and Callables along with a validation schema. _state_ok is used to define which of the states are considered good, and when objects are in a group to define whether the group status is ok. This is displayed as a green status symbol in the synoptic view for instance.

Then create the controls system specific implementation. In the case of Bliss this is a simple mapping between the Bliss objects properties / functions and the abstract object.

# daiquiri/core/hardware/bliss/motor.py
from daiquiri.core.hardware.abstract import HardwareProperty
from daiquiri.core.hardware.abstract.motor import Motor as AbstractMotor, MotorStates
from daiquiri.core.hardware.bliss.object import BlissObject


class PositionProperty(HardwareProperty):
    def translate_from(self, value):
        return round(value, 4)

class StateProperty(HardwareProperty):
    def translate_from(self, value):
        states = []
        for s in MotorStates:
            if s in value:
                states.append(s)

        if len(states):
            return states

        return ["UNKNOWN"]

class Motor(BlissObject, AbstractMotor):
    PROPERTY_MAP = {
        "position": PositionProperty("position"),
        ...
        "unit": HardwareProperty("unit"),
    }

    CALLABLE_MAP = {"stop": "stop", "wait": "wait_move"}

A specific transformation between the control system and Daiquiri abstract object properties is provided by HardwareProperty. Methods translate_from and translate_to can be re-impremented.

This allows the object to translate between for example abstract states and the control system states. For a motor this might be as simple as converting an Bliss objects object.Ready to a 'READY' string.

Finally for Bliss objects a mapping must be made between the defined class and the class of the Bliss object. This can be done by appending to _class_map in daiquiri/core/hardware/bliss/__init__.py.

_class_map = {
    # Bliss class: daiquiri class
    "Axis": "motor",
    ...
}

For tango objects the class is auto loaded from the type property defined in ghardware.yml

  - name: lima
    id: lima_simulator
    protocol: tango
    type: lima
    tango_url: tango://localhost:20000/id00/limaccds/simulator1

Module Resolution

The loader expects the daiquiri classname in lower case. It will resolve to, for example:

  • motor -> bliss.motor.Motor
  • measurementgroup -> bliss.measurementgroup.Measurementgroup
  • lima -> tango.lima.Lima