Source code for instruments.thorlabs.pm100usb

#!/usr/bin/env python
"""
Provides the support for the Thorlabs PM100USB power meter.
"""

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


import logging
from collections import defaultdict, namedtuple

from enum import Enum, IntEnum

from instruments.units import ureg as u

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: """ 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.query("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 & int(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(f"SENS:AVER:COUN {newval}") # METHODS ## _READ_UNITS = defaultdict(lambda: u.dimensionless) _READ_UNITS.update( { MeasurementConfiguration.power: u.W, MeasurementConfiguration.current: u.A, MeasurementConfiguration.frequency: u.Hz, MeasurementConfiguration.voltage: u.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:`~pint.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