Source code for experimentor.lib.device

# -*- coding: utf-8 -*-
"""
    device.py
    =========
    Devices are connected to the computer. They control sensors and actuators. A device has to be able to set and read
    values.
    Setting complex devices such as a laser would require to define it as a device and its properties as sensors or
    actuators respectively.

    .. warning::

        If problems arise when adding new devices, tt is important to check :meth:initialize_driver .
        It was hardcoded which parameters are passed when initializing each device type.

    .. todo::

        Make flexible parameters when initializing the driver of the devices.

    .. sectionauthor:: Aquiles Carattino
"""
import importlib
import logging

from .actuator import Actuator
from .sensor import Sensor
from .. import Q_

logger = logging.getLogger(__name__)


[docs]class Device: """ Device is responsible for the communication with real devices. Device takes only one argument, a dictionary of properties, including the driver. Device has two properties, one called _properties that stores the initial properties passed to the device and is read-only. _params stores the parameters passed during execution; it doesn't store a history, just the latest one. """ def __init__(self, properties): if 'name' in properties: logger.debug('Loaded properties of {}'.format(properties['name'])) self._name = properties['name'] else: logger.debug('Loaded properties of device without name') self._name = 'nameless' self._properties = properties self.driver = None self._params = {}
[docs] def add_driver(self, driver): """ Adds the driver of the device. It has to be initialized() :param driver: driver of any class. :return: Null """ self.driver = driver logger.debug('Added driver to {}'.format(self._name))
[docs] def initialize_driver(self): """ Initializes the driver. There are 4 types of possible connections: - GPIB - USB - serial - daq The first 3 are based on Lantz and its initialization routine, while daq was inherited from previous code and has a different initialization routine.""" if 'driver' in self._properties: d = self._properties['driver'].split('/') driver_class = getattr(importlib.import_module(d[0]), d[1]) if 'connection' in self._properties: connection_type = self._properties['connection']['type'] logger.debug('Initializing {} connection'.format(connection_type)) try: if connection_type == 'GPIB': # Assume it is a lantz driver self.driver = driver_class.via_gpib(self._properties['connection']['port']) self.driver.initialize() elif connection_type == 'USB': # Assume it is a lantz driver self.driver = driver_class.via_usb() self.driver.initialize() logger.warning('Connection {} was never tested.'.format(connection_type)) raise Warning('This was never tested!') elif connection_type == 'serial': # Assume it is a lantz driver self.driver = driver_class.via_serial(self._properties['connection']['port']) self.driver.initialize() logger.warning('Connection {} was never tested.'.format(connection_type)) raise Warning('This was never tested!') elif connection_type == 'daq': self.driver = driver_class(self._properties['connection']['port']) except: logger.error('{} driver for {} not initialized'.format(connection_type, self._name)) raise Exception('Driver not initialized')
[docs] def apply_values(self, values): """ Iterates over all values of a dictionary and sets the values of the driver to it. It is kept for legacy support but it is very important to switch to apply_value, passing an actuator. .. warning:: This method can misbehave with the new standards of sensors and actuators in place since version 0.1. :param values: a dictionary of parameters and desired values for those parameters. The parameters should have units. """ if self.driver is None: logger.error('Trying to apply values before initializing the driver') raise Exception('Driver not yet initialized') if isinstance(values, dict): for k in values: if not isinstance(values[k], Q_): try: # Tries to convert to proper units, if it fails it uses the value as is value = Q_(values[k]) except: logger.warning('Value {} could not be converted to Quantity'.format(values[k])) value = values[k] logger.info('Setting {} to {:~}'.format(k, value)) try: setattr(self.driver, k, values[k]) except: logger.error('Problem setting %s in %s' % (k, self)) self._params[k] = value else: logger.error('Drivers can only update dictionaries') raise Exception('Drivers can only update dictionaries')
[docs] def apply_value(self, actuator, value): """ Applies a given value to an actuator through the driver of the device. It is only a relay function left here to keep the hierarchical structure of the program, i.e. actuators communicate with devices, devices communicate with models and models with drivers. :param actuator: instance of Actuator :param value: A value to be set. Ideally a Quantity. """ if not isinstance(actuator, Actuator): err_str = "Trying to update the value of {} and not of an Actuator".format(type(actuator)) logger.error(err_str) raise Exception(err_str) if not isinstance(value, Q_): logger.info("Passing value {} to {} and that is not a Quantity".format(value, actuator.name)) self.driver.apply_value(actuator, value)
[docs] def read_value(self, sensor): """ Reads a value from a sensor. This method is just a relay to a model, in order to keep the structure of the program tidy. """ if not isinstance(sensor, Sensor): err_str = "Trying to read the value of {} and not of a Sensor".format(type(sensor)) logger.error(err_str) raise Exception(err_str) return self.driver.read_value(sensor)
@property def params(self): return self._params @property def properties(self): return self._properties def __str__(self): return self._name