Skip to content

Common arguments to all properties

API Reference

allow_None, constant & readonly

  • if allow_None is True, property supports None apart from its own type
  • readonly (being True) makes the property read-only or execute only the getter method
  • constant (being True), again makes the property read-only but can be set once if allow_None is True. This is useful to set the property once at __init__() but remain constant after that.
allow None, constant and readonly
from hololinked.core import Thing
from hololinked.core.properties import String, Number, TypedList


class OceanOpticsSpectrometer(Thing):
    """Spectrometer example object"""

    serial_number = String(
        default="USB2+H15897",
        allow_None=False,
        readonly=True,
        doc="serial number of the spectrometer (string)",
        label="serial number",
    )  # type: str

    integration_time = Number(
        default=1000,
        bounds=(0.001, None),
        allow_None=False,
        crop_to_bounds=True,
        label="Integration Time (ms)",
        doc="integration time of measurement in milliseconds",
    )

    model = String(
        default=None,
        allow_None=True,
        constant=True,
        label="device model",
        doc="model of the connected spectrometer",
    )

    custom_background_intensity = TypedList(
        item_type=(float, int),
        default=None,
        allow_None=True,
        label="Custom Background Intensity",
        doc="user provided background substraction intensity",
    )
allow None, constant and readonly
1
2
3
4
# allow_None
spectrometer.custom_background_intensity = None  # OK
spectrometer.custom_background_intensity = []  # OK
spectrometer.custom_background_intensity = None  # OK
allow None, constant and readonly
1
2
3
# allow_None = False
spectrometer.integration_time = None  # NOT OK, raises TypeError
spectrometer.integration_time = 2000  # OK
allow None, constant and readonly
# readonly = True
spectrometer.serial_number = "USB2+H15897"  # NOT OK - raises ValueError
allow None, constant and readonly
1
2
3
4
# constant = True, mandatorily needs allow_None = True
spectrometer.model = None  # OK - constant accepts None when initially None
spectrometer.model = "USB2000+"  # OK - can be set once
spectrometer.model = None  # NOT OK - raises ValueError

doc and label

doc allows clients to fetch a docstring for the property. label can be used to show the property in a GUI with a readable name (for example).

allow None, constant and readonly
class OceanOpticsSpectrometer(Thing):
    """Spectrometer example object"""

    serial_number = String(
        default="USB2+H15897",
        allow_None=False,
        readonly=True,
        doc="serial number of the spectrometer (string)",
        label="serial number",
    )  # type: str

default, fget, fset & fdel

To provide a getter-setter (& deleter) method is optional.

If none given, when the property is set/written, the value is stored inside the instance's __dict__ under the name <given property name>_param_value (for example, serial_number_param_value for serial_number). In layman's terms, __dict__ is the internal map where the attributes of the object are stored by python.

When a value assignment was never called on the property, default is returned when reading the value. If a setter/deleter is given, getter is mandatory. In this case, default is also ignored & the getter is always executed.

class IDSCamera(Thing):
    """Camera example object"""

    frame_rate = Number(
        default=1,
        bounds=(0, 40),
        doc="frame rate of the camera",
        crop_to_bounds=True,
    )

    @frame_rate.setter
    def set_frame_rate(self, value):
        setFPS = ueye.double()
        ret = ueye.is_SetFrameRate(self.device, value, setFPS)
        if ret != ueye.IS_SUCCESS:
            raise Exception("could not set frame rate")

    @frame_rate.getter
    def get_frame_rate(self) -> float:
        getFPS = ueye.double()
        doc="frame rate of the camera",
        fget=get_frame_rate,
        fset=set_frame_rate,
    )

    id = Integer(
        default=1,  # not ignored
        allow_None=True,
        bounds=(1, 255),
        doc="Camera ID shown in IDS Camera Manager (not dev. ID)",
    )

if __name__ == "__main__":
    cam = IDSCamera(id="camera")
    print(cam.id)  # prints 1
    print(cam.frame_rate)  # does not print default,
    # but the actual value in device after invoking the getter
class IDSCamera(Thing):
    """Camera example object"""

    def set_frame_rate(self, value):
        setFPS = ueye.double()
        ret = ueye.is_SetFrameRate(self.device, value, setFPS)
        if ret != ueye.IS_SUCCESS:
            raise Exception("could not set frame rate")

    def get_frame_rate(self) -> float:
        getFPS = ueye.double()
        ret = ueye.is_SetFrameRate(self.device, ueye.IS_GET_FRAMERATE, getFPS)
        if ret != ueye.IS_SUCCESS:
            raise Exception("could not get frame rate")
        return getFPS.value

    frame_rate = Number(
        default=1,  # ignored
        bounds=(0, 40),
        crop_to_bounds=True,
        doc="frame rate of the camera",
        fget=get_frame_rate,
        fset=set_frame_rate,
    )

If default is desirable, one has to return it manually in the getter method by accessing the property descriptor object directly.

class_member

If class_member is True, the value is set in the class' __dict__ (i.e. becomes a class attribute) instead of instance's __dict__ (instance's attribute). Custom getter-setter-deleter are not compatible with this option currently. class_member takes precedence over fget-fset-fdel, which in turn has precedence over default.

class member
class ErrorCodes(IntEnum):
    IS_NO_SUCCESS = -1
    IS_SUCCESS = 0
    IS_INVALID_CAMERA_HANDLE = 1
    IS_CANT_OPEN_DEVICE = 3
    IS_CANT_CLOSE_DEVICE = 4

    @classmethod
    def json(cls):
        # code to code name - opposite of enum definition
        return {
            value.value: name
            for name, value in vars(cls).items()
            if isinstance(value, cls)
        }

class IDSCamera(Thing):
    """Camera example object"""

    def error_codes_misplaced_getter(self):
        return {"info": "this getter is never called"}

    error_codes = Property(
        readonly=True,
        default=ErrorCodes.json(),
        class_member=True,
        doc="error codes raised by IDS library",
        fget=error_codes_misplaced_getter,  # ignored
    )

if __name__ == "__main__":
    cam = IDSCamera(id="camera")
    print("errorcodes class level", IDSCamera.error_codes)  # prints error codes
    print("errorcodes instance level", cam.error_codes)  # prints error codes
    print(IDSCamera.error_codes == cam.error_codes)  # prints True

class_member can still be used with a default value if there is no custom fget-fset-fdel.

remote

setting remote to False makes the property inaccessible to a client but accessible to the object locally. This is still useful to type-restrict python attributes to provide an interface to other developers using your class, for example, when someone else inherits your Thing. For example, the Thing's logger is implemented in this fashion:

local properties
import logging
from hololinked.core.properties import ClassSelector

class Thing(metaclass=ThingMeta):
    """Subclass from here to expose hardware or python objects on the network"""

    logger = ClassSelector(
        class_=logging.Logger,
        default=None,
        allow_None=True,
        remote=False,  # does not make sense to expose the logger object
        doc="""logging.Logger instance to print log messages.
            Default logger with a IO-stream handler and network
            accessible handler is created if none supplied."""
    )  # type: logging.Logger

state

When state is specifed, the property is writeable only when the Thing's StateMachine is in that specified state (or in the list of allowed states):

state machine state
class IDSCamera(Thing):
    """Camera example object"""

    def get_pixelclock(self) -> int:
        cint_in = ueye.uint()
        ret = ueye.is_PixelClock(
            self.handle,
            ueye.IS_PIXELCLOCK_CMD_GET,
            cint_in,
            ueye.sizeof(cint_in),
        )
        assert return_code_OK(self.handle, ret)
        return cint_in.value

    def set_pixelclock(self, value: int) -> None:
        cint_in = ueye.uint(value)
        ret = ueye.is_PixelClock(
            self.handle,
            ueye.IS_PIXELCLOCK_CMD_SET,
            cint_in,
            ueye.sizeof(cint_in),
        )
        assert return_code_OK(self.handle, ret)

    pixel_clock = Integer(
        doc="Pixel clock in MHz",
        bounds=(0, None),
        state=["ON"],
        metadata=dict(unit="MHz"),
        inclusive_bounds=(False, True),
        fget=get_pixelclock,
        fset=set_pixelclock,
    )  # type: int

This is also currently applicable only when write operations are called by clients. Local write operations are always executed irrespective of the state machine state. A read operation is always executed even from the clients irrespective of the state.

observable

Observable properties push change events when the property is set or read. This is useful when one wants to monitor the property for changes without polling from the client. The payload of the change event is the new value of the property.

observable
class IDSCamera(Thing):
    """Camera example object"""

    def set_exposure(self, value: float) -> None:
        cdbl_in = ueye.double(value)
        ret = ueye.is_Exposure(
            self.handle,
            ueye.IS_EXPOSURE_CMD_SET_EXPOSURE,
            cdbl_in,
            ueye.sizeof(cdbl_in),
        )
        assert return_code_OK(self.handle, ret)

    def get_exposure(self) -> float:
        cdbl_out = ueye.double()
        ret = ueye.is_Exposure(
            self.handle,
            ueye.IS_EXPOSURE_CMD_GET_EXPOSURE,
            cdbl_out,
            ueye.sizeof(cdbl_out),
        )
        assert return_code_OK(self.handle, ret)
        return cdbl_out.value

    exposure_time = Number(
        bounds=(0, None),
        inclusive_bounds=(False, True),
        doc="Exposure time for image in milliseconds",
        observable=True,
        metadata=dict(unit="ms"),
        fget=get_exposure,
        fset=set_exposure,
    )  # type: float

if __name__ == "__main__":
    cam = IDSCamera(id="camera")

    for i in range(10):
        cam.exposure_time = i * 10.0  # pushes change event to a client
        cam.capture_image()  # captures an image with current exposure time

metadata

metadata is a free-flow dictionary that allows storing arbitrary metadata about the property. For example, one can store units of the physical quantity.

db_init, db_commit & db_persist

Properties can be stored in a file or a database and loaded from them when the Thing is stopped and restarted. This is useful especially to preserve the settings of the hardware when the server undergoes a restart, either through system restart, server crash or any other reason.

  • db_init only loads a property from database. When the property value is changed, its not written back to the database. For this option, the value has to be pre-created in the database in some other fashion.

  • db_commit only writes the value into the database when an assignment/write operation is called.

  • db_persist stores and loads the property from the database. property value is assigned after __init__() method, therefore its recommended to ensure that the device connection is established at __init__() itself. Otherwise, errors should be expected.

Supported databases are MySQL, Postgres, SQLite & Mongo currently. Look at database how-to for supply database configuration.