Source code for instruments.thorlabs.pm100usb

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Provides the support for the Thorlabs PM100USB power meter.
"""

# IMPORTS #####################################################################

from __future__ import absolute_import
from __future__ import division

import logging
from collections import defaultdict, namedtuple

from enum import Enum, IntEnum

import quantities as pq

from instruments.generic_scpi import SCPIInstrument
from instruments.util_fns import enum_property

# LOGGING #####################################################################

logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())

# CLASSES #####################################################################


[docs]class PM100USB(SCPIInstrument): """ Instrument class for the `ThorLabs PM100USB`_ power meter. Note that as this is an SCPI-compliant instrument, the properties and methods of :class:`~instruments.generic_scpi.SCPIInstrument` may be used as well. .. _ThorLabs PM100USB: http://www.thorlabs.com/thorproduct.cfm?partnumber=PM100USB """ # ENUMS #
[docs] class SensorFlags(IntEnum): """ Enum containing valid sensor flags for the PM100USB """ is_power_sensor = 1 is_energy_sensor = 2 response_settable = 16 wavelength_settable = 32 tau_settable = 64 has_temperature_sensor = 256
[docs] class MeasurementConfiguration(Enum): """ Enum containing valid measurement modes for the PM100USB """ current = "CURR" power = "POW" voltage = "VOLT" energy = "ENER" frequency = "FREQ" power_density = "PDEN" energy_density = "EDEN" resistance = "RES" temperature = "TEMP"
# We will cheat and also represent things by a named tuple over bools. # TODO: make a flagtuple into a new type in util_fns, copying this out # as a starting point. _SensorFlags = namedtuple("SensorFlags", [ flag.name for flag in SensorFlags # pylint: disable=not-an-iterable ]) # INNER CLASSES #
[docs] class Sensor(object): """ Class representing a sensor on the ThorLabs PM100USB .. warning:: This class should NOT be manually created by the user. It is designed to be initialized by the `PM100USB` class. """ def __init__(self, parent): self._parent = parent # Pull details about the sensor from SYST:SENSOR:IDN? sensor_idn = parent.sendcmd("SYST:SENSOR:IDN?") ( self._name, self._serial_number, self._calibration_message, self._sensor_type, self._sensor_subtype, self._flags ) = sensor_idn.split(",") # Normalize things to enums as appropriate. # We want flags to be a named tuple over bools. # pylint: disable=protected-access self._flags = parent._SensorFlags(**{ e.name: bool(e & self._flags) for e in PM100USB.SensorFlags # pylint: disable=not-an-iterable }) @property def name(self): """ Gets the name associated with the sensor channel :type: `str` """ return self._name @property def serial_number(self): """ Gets the serial number of the sensor channel :type: `str` """ return self._serial_number @property def calibration_message(self): """ Gets the calibration message of the sensor channel :type: `str` """ return self._calibration_message @property def type(self): """ Gets the sensor type of the sensor channel :type: `str` """ return self._sensor_type, self._sensor_subtype @property def flags(self): """ Gets any sensor flags set on the sensor channel :type: `collections.namedtuple` """ return self._flags
# PRIVATE ATTRIBUTES # _cache_units = False # UNIT CACHING # @property def cache_units(self): """ If enabled, then units are not checked every time a measurement is made, reducing by half the number of round-trips to the device. .. warning:: Setting this to `True` may cause incorrect values to be returned, if any commands are sent to the device either by its local panel, or by software other than InstrumentKit. :type: `bool` """ return bool(self._cache_units) @cache_units.setter def cache_units(self, newval): self._cache_units = ( self._READ_UNITS[self.measurement_configuration] if newval else False ) # SENSOR PROPERTIES # @property def sensor(self): """ Returns information about the currently connected sensor. :type: :class:`PM100USB.Sensor` """ return self.Sensor(self) # SENSING CONFIGURATION PROPERTIES # # TODO: make a setting of this refresh cache_units. measurement_configuration = enum_property( "CONF", MeasurementConfiguration, doc=""" Returns the current measurement configuration. :rtype: :class:`PM100USB.MeasurementConfiguration` """ ) @property def averaging_count(self): """ Integer specifying how many samples to collect and average over for each measurement, with each sample taking approximately 3 ms. """ return int(self.query("SENS:AVER:COUN?")) @averaging_count.setter def averaging_count(self, newval): if newval < 1: raise ValueError("Must count at least one time.") self.sendcmd("SENS:AVER:COUN {}".format(newval)) # METHODS ## _READ_UNITS = defaultdict(lambda: pq.dimensionless) _READ_UNITS.update({ MeasurementConfiguration.power: pq.W, MeasurementConfiguration.current: pq.A, MeasurementConfiguration.frequency: pq.Hz, MeasurementConfiguration.voltage: pq.V, })
[docs] def read(self, size=-1, encoding='utf-8'): """ Reads a measurement from this instrument, according to its current configuration mode. :param int size: Number of bytes to read from the instrument. Default of ``-1`` reads until a termination character is found. :units: As specified by :attr:`~PM100USB.measurement_configuration`. :rtype: :class:`~quantities.Quantity` """ # Get the current configuration to find out the units we need to # attach. units = ( self._READ_UNITS[self.measurement_configuration] if not self._cache_units else self._cache_units ) return float(self.query('READ?', size)) * units