Source code for lightlab.equipment.lab_instruments.ILX_7900B_LS

from . import VISAInstrumentDriver
from lightlab.laboratory.instruments import LaserSource

import numpy as np
import time

from lightlab.equipment.abstract_drivers import ConfigModule, MultiModuleConfigurable
from lightlab.util.data import Spectrum
from lightlab import logger


[docs]class ILX_Module(ConfigModule): ''' Handles 0 to 1 indexing ''' def __init__(self, channel, **kwargs): kwargs['precedingColon'] = kwargs.pop('precedingColon', False) super().__init__(channel=channel + 1, **kwargs)
[docs]class ILX_7900B_LS(VISAInstrumentDriver, MultiModuleConfigurable): ''' The laser banks (ILX 7900B laser source). Provides array-based and dict-based setters/getters for * whether laser is on or off (``enableState``) * tunable wavelength output (``wls``) * output power in dBm (``powers``) Setting/getting logic is implemented in ``MultiModuleConfigurable``, which treats the channels as independent ``ConfigModules``'s. This means that hardware communication is lazy -- parameter values are cached, and messages are only sent when they are unknown or when they change. `Manual <http://assets.newport.com/webDocuments-EN/images/70032605_FOM-79800F_IX.PDF>`_ Usage: :ref:`/ipynbs/Hardware/LaserSources-ILX.ipynb` Todo: Multiple users at the same time is desirable. We are close. Non blocked-out channels are never touched, but there are still two issues * Fundamental: VISA access with two python processes could collide * Inconvenience: Have to create two different labstate instruments with different ``useChans`` for what is actually one instrument -- maybe a slice method? ''' instrument_category = LaserSource maxChannel = 8 # Time it takes to equilibrate on different changes, in seconds sleepOn = dict(OUT=3, WAVE=30, LEVEL=5) powerRange = np.array([-20, 13]) def __init__(self, name='The laser source', address=None, useChans=None, **kwargs): kwargs['tempSess'] = kwargs.pop('tempSess', False) if 'dfbChans' in kwargs.keys(): useChans = kwargs.pop('dfbChans') if useChans is None: logger.warning('No useChans specified for ILX_7900B_LS') useChans = list() VISAInstrumentDriver.__init__(self, name=name, address=address, **kwargs) MultiModuleConfigurable.__init__(self, useChans=useChans, configModule_klass=ILX_Module)
[docs] def startup(self): self.close() # For temporary serial access
@property def dfbChans(self): ''' Returns the blocked out channels as a list Currently, this is not an essentialProperty, so you have to access like:: ch = LS.driver.dfbChans Returns: (list): channel numbers, 0-indexed ''' return self.useChans
[docs] def setConfigArray(self, cStr, newValArr, forceHardware=False): ''' When any configuration is set, there is an equilibration time. This adds sleep functionality, only when there is a change, for an amount determined by the ``sleepOn`` class attribute. ''' wroteToHardware = super().setConfigArray(cStr, newValArr, forceHardware=forceHardware) if wroteToHardware: print('DFB settling for', self.sleepOn[cStr], 'seconds.') time.sleep(self.sleepOn[cStr]) print('done.')
# Module-level parameter setters and getters. @property def enableState(self): ''' Returns: (np.ndarray): enable states ordered like useChans ''' return self.getConfigArray('OUT') @enableState.setter def enableState(self, newState): ''' Are lasers on or off? Provides range check. Args: newState (list, np.ndarray): enable values which must be 0 or 1 ''' for ena in newState: if ena not in [0, 1]: raise ValueError('Laser states can only be 0 or 1. ' + 'Got {}'.format(newState)) self.setConfigArray('OUT', newState)
[docs] def setChannelEnable(self, chanEnableDict): ''' Sets only some channel values with dict keyed by useChans, e.g. ``chanEnableDict={0: 1, 2: 0}`` Args: chanEnableDict (dict): A dictionary keyed by channel with values 0 or 1 ''' self.setConfigDict('OUT', chanEnableDict)
[docs] def getChannelEnable(self): ''' Returns: (dict): all channel enable states, keyed by useChans ''' return self.getConfigDict('OUT')
@property def wls(self): ''' Returns: (np.ndarray): laser wavelengths in nanometers ordered like useChans ''' return self.getConfigArray('WAVE') @wls.setter def wls(self, newWls): ''' Laser wavelengths. Provides range check. Args: newWls (list, np.ndarray): wavelengths in nanometers ''' for iCh, wl in enumerate(newWls): wlRanges = self.wlRanges[iCh] if wl < wlRanges[0]: logger.warning('Wavelength out of range was constrained:\n' + 'Requested: {:.2f}nm '.format(wl) + 'Minimum: {:.2f}nm.'.format(wlRanges[0])) newWls[iCh] = wlRanges[0] if wl > wlRanges[1]: logger.warning('Wavelength out of range was constrained:\n' + 'Requested: {:.2f}nm '.format(wl) + 'Maximum: {:.2f}nm.'.format(wlRanges[1])) newWls[iCh] = wlRanges[1] self.setConfigArray('WAVE', newWls)
[docs] def setChannelWls(self, chanWavelengthDict): ''' Sets only some channel values with dict keyed by useChans, e.g. ``chanWavelengthDict={0: 1550.5, 2: 1551}`` Args: chanWavelengthDict (dict): A dictionary keyed by channel with nanometer values ''' self.setConfigDict('WAVE', chanWavelengthDict)
[docs] def getChannelWls(self): ''' Returns: (dict): all channel wavelengths, keyed by useChans ''' return self.getConfigDict('WAVE')
@property def powers(self): ''' Laser powers Returns: (np.ndarray): laser output powers in dBm, ordered like useChans ''' return self.getConfigArray('LEVEL') @powers.setter def powers(self, newPowers): ''' Laser powers. Provides range check. Args: newPowers (list, np.ndarray): power in dBm ''' for iCh, level in enumerate(newPowers): if level < self.powerRange[0]: logger.warning('Power out of range was constrained:\n' + 'Requested: {:.2f}dBm '.format(level) + 'Minimum: {:.2f}dBm.'.format(self.powerRange[0])) newPowers[iCh] = self.powerRange[0] if level > self.powerRange[1]: logger.warning('Power out of range was constrained:\n' + 'Requested: {:.2f}dBm '.format(level) + 'Maximum: {:.2f}dBm.'.format(self.powerRange[1])) newPowers[iCh] = self.powerRange[1] self.setConfigArray('LEVEL', newPowers)
[docs] def setChannelPowers(self, chanPowerDict): ''' Sets only some channel values with dict keyed by useChans, e.g. ``chanPowerDict={0: 13, 2: -10}`` Args: chanPowerDict (dict): A dictionary keyed by channel with dBm values ''' self.setConfigDict('LEVEL', chanPowerDict)
[docs] def getChannelPowers(self): ''' Returns: (dict): all channel powers, keyed by useChans ''' return self.getConfigDict('LEVEL')
@property def wlRanges(self): ''' Min/max wavelengths than can be covered by each channl. Wavelengths in nm. Returns: (list(tuple)): maximum ranges starting from lower wavelength ''' minArr = self.getConfigArray('WAVEMIN') maxArr = self.getConfigArray('WAVEMAX') return tuple(zip(minArr, maxArr))
[docs] def getAsSpectrum(self): ''' Gives a spectrum of power vs. wavelength, which has the wavelengths present as an abscissa, and their powers as ordinate (-120dBm if disabled) It starts in dBm, but you can change to linear with the Spectrum.lin method Returns: (Spectrum): The WDM spectrum of the present outputs ''' absc = self.wls ordi = self.powers.copy() for iCh, ena in enumerate(self.enableState): if ena == 0: ordi[iCh] = -120 return Spectrum(absc, ordi, inDbm=True)
[docs] def allOff(self): self.off()
[docs] def allOn(self): self.enableState = np.ones(len(self.useChans))
[docs] def off(self): self.enableState = np.zeros(len(self.useChans))