def __init__(self, name='Unnamed Host', hostname=None, **kwargs): if hostname is None: logger.warning("Hostname not set. isLive and list_resources not functional.") self.hostname = hostname self.name = name super().__init__(**kwargs)
def isLive(self): """ Attempts VISA connection to instrument, and checks whether :meth:`~lightlab.equipment.visa_bases.visa_object.instrID` matches :data:`id_string`. Produces a warning if it is live but the id_string is wrong. Returns: (bool): True if "live", False otherwise. """ try: driver = self.driver_object query_id = driver.instrID() logger.info("Found %s in %s.", self.name, self.address) if self.id_string is not None: if self.id_string == query_id: logger.info("id_string of %s is accurate", self.name) return True else: logger.warning("%s: %s, expected %s", self.address, query_id, self.id_string) return False else: logger.debug("Cannot authenticate %s in %s.", self.name, self.address) return True except pyvisa.VisaIOError as err: logger.warning(err) return False
def __init__(self, real_obj=None, virt_obj=None): ''' Args: real_obj (Instrument): the real reference virt_obj (VirtualInstrument): the virtual reference ''' self.real_obj = real_obj self.virt_obj = virt_obj if real_obj is not None and virt_obj is not None: violated = [] allowed = real_obj.essentialMethods + \ real_obj.essentialProperties + dir(VirtualInstrument) for attr in dir(type(virt_obj)): if attr not in allowed \ and '__' not in attr: violated.append(attr) if len(violated) > 0: logger.warning('Virtual instrument ({}) violates '.format( type(virt_obj).__name__) + 'interface of the real one ({})'.format( type(real_obj).__name__)) logger.warning('Got: ' + ', '.join(violated)) # pylint: disable=logging-not-lazy # logger.warning('Allowed: ' + ', '.join(filter(lambda x: '__' not in x, allowed))) self.synced = [] super().__init__()
def frequency(self, freq=None): ''' Frequency is in Hertz **Setting the frequency takes you out of sweep mode automatically** Args: freq (float): If None, only gets Returns: (float): center frequency ''' if freq is not None: if freq > 40e9: logger.warning( 'Agilent N5183 ony goes up to 40GHz, given %s GHz.', freq / 1e9) freq = 40e9 if self.sweepEnable(): logger.warning( 'Agilent N5183 was sweeping when you set frequency, moving to CW mode' ) self.setConfigParam( 'FREQ:CW', freq) # Setting this automatically brings to CW mode # So we need to update this object's internal state too self.sweepEnable(False) return self.getConfigParam('FREQ:CW')
def check_if_port_is_not_connected(connection, k1, v1): for k2, v2 in connection.items(): if (k1, v1) == (k2, v2): logger.warning("Deleting existing connection %s.", connection) return False return True
def measVoltage(self): retStr = self.query('MEASURE:VOLT?') v = float(retStr.split(',')[0]) # first number is voltage always if v >= self.protectionVoltage: logger.warning('Keithley compliance voltage of %s reached', self.protectionVoltage) logger.warning('You are sourcing %smW into the load.', v * self._latestCurrentVal * 1e-3) return v
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)
def measCurrent(self): retStr = self.query('MEASURE:CURR?') i = float(retStr.split(',')[1]) # second number is current always if i >= self.protectionCurrent: logger.warning('Keithley compliance current of %s reached', self.protectionCurrent) logger.warning('You are sourcing %smW into the load.', i * self._latestVoltageVal * 1e-3) return i
def updateConnections(self, *connections): """ Updates connections between instruments and devices. A connection is a tuple with a pair of one-entry dictionaries, as such: .. code-block:: python conn = ({instr1: port1}, {instr2: port2}) The code assumes that there can only be one connection per port. This method performs the following action: 1. verifies that `port` is one of `instr.ports`. Otherwise raises a ``RuntimeError``. 2. deletes any connection in ``lab.connections`` that has either ``{instr1: port1}`` or ``{instr1: port1}``, and logs the deleted connection as a warning. 3. adds new connection Args: connections (tuple(dict)): connection to update """ # Verify if ports are valid, otherwise do nothing. for connection in connections: for k1, v1 in connection.items(): if v1 not in k1.ports: logger.error("Port '%s' is not in '%s: %s'", v1, k1, k1.ports) raise RuntimeError("Port '{}' is not in '{}: {}'".format( v1, k1, k1.ports)) # Remove old conflicting connections def check_if_port_is_not_connected(connection, k1, v1): for k2, v2 in connection.items(): if (k1, v1) == (k2, v2): logger.warning("Deleting existing connection %s.", connection) return False return True for connection in connections: for k1, v1 in connection.items(): connectioncheck2 = lambda connection: check_if_port_is_not_connected( connection, k1, v1) self.connections[:] = [ x for x in self.connections if connectioncheck2(x) ] # Add new connections for connection in connections: if connection not in self.connections: self.connections.append(connection) else: logger.warning("Connection already exists: %s", connection) return True
def measCurrent(self): retStr = self.query_print( "{smuX}.measure.i()".format(smuX=self.smu_full_string)) i = float(retStr) # second number is current always if self.compliance: logger.warning('Keithley compliance current of %s reached', self.protectionCurrent) logger.warning('You are sourcing %smW into the load.', i * self._latestVoltageVal * 1e-3) return i
def measVoltage(self): retStr = self.query_print( "{smuX}.measure.v()".format(smuX=self.smu_full_string)) v = float(retStr) if self.compliance: logger.warning('Keithley compliance voltage of %s reached', self.protectionVoltage) logger.warning('You are sourcing %smW into the load.', v * self._latestCurrentVal * 1e-3) return v
def driver_class(self): """ Class of the actual equipment driver (from :mod:`lightlab.equipment.lab_instruments`) This way the object knows how to instantiate a driver instance from the labstate. """ if self._driver_class is None: logger.warning("Using default driver for %s.", self) return DefaultDriver else: return self._driver_class
def isLive(self): ''' Pings the system and returns if it is alive. ''' if self.hostname is not None: logger.debug("Pinging %s...", self.hostname) response = os.system("ping -c 1 {}".format(self.hostname)) if response != 0: logger.warning("%s is not reachable via ping.", self) return response == 0 else: logger.warning("Hostname not set. Unable to ping.") return False
def __init__(self, useChans=None, **kwargs): if useChans is None: logger.warning('No useChans specified for MultichannelSource') useChans = list() self.useChans = useChans self.stateDict = dict([ch, 0] for ch in self.useChans) # Check that the requested channels are available to be blocked out if self.maxChannel is not None: if any(ch > self.maxChannel - 1 for ch in self.useChans): raise ChannelError( 'Requested channel is more than there are available') super().__init__(**kwargs)
def removeInstrument(self, *instruments): r""" Disconnects the instrument from the host Args: \*instruments (Instrument): instruments Todo: Remove all connections """ for instrument in instruments: if type(instrument) is str: logger.warning('Cannot remove by name string. Use the object') instrument.host = None
def saveState(self, fname=None, save_backup=True): """ Saves the current lab, together with all its dependencies, to a JSON file. But first, it checks whether the file has the same hash as the previously loaded one. If file is not found, skip this check. If the labstate was created from scratch, save with ``_saveState()``. Args: fname (str or Path): file path to save save_backup (bool): saves a backup just in case, defaults to True. Raises: OSError: if there is any problem saving the file. """ if fname is None: fname = self.filename try: loaded_lab = LabState.loadState(fname) except FileNotFoundError: logger.debug("File not found: %s. Saving for the first time.", fname) self._saveState(fname, save_backup=False) return except JSONDecodeError: if os.stat(fname).st_size == 0: logger.warning("%s is empty. Saving for the first time.", _filename) self._saveState(fname, save_backup=False) return else: raise if not self.__sha256__: logger.debug( "Attempting to compare fabricated labstate vs. preloaded one.") self.__sha256__ = self.__toJSON()["__sha256__"] logger.debug("self.__sha256__: %s", self.__sha256__) if loaded_lab == self: logger.debug("Detected no changes in labstate. Nothing to do.") return if loaded_lab.__sha256__ == self.__sha256__: self._saveState(fname, save_backup) else: logger.error( "%s's hash does not match with the one loaded in memory. Aborting save.", fname)
def instrument_hooked(instrument=instrument, host=host, bench=bench): and_expr = True if host is not None: expr = (instrument in host) if not expr: logger.warning("{} not in {} %s", (instrument, host)) and_expr = and_expr and expr if bench is not None: expr = (instrument in bench) if not expr: logger.warning("{} not in {} %s", (instrument, bench)) and_expr = and_expr and expr return and_expr
def insertDevice(self, device): """ Inserts device in labstate. Args: device (Device): device to insert. Raises: RuntimeError: Raised if duplicate names are found. TypeError: Raised if device is not of type :class:`~lightlab.laboratory.instruments.bases.Device` """ self.devices.append(device) if device.bench and device.bench not in self.benches: logger.warning(f"Insterting *new* bench {device.bench.name}") self.benches.append(device.bench)
def registerConnections(self, *connections): for connection in connections: if connection not in self.connections: self.connections.append(connection) else: logger.warning("Connection already exists: %s", connection) def connection_present(connection=connection, connections=self.lab.connections): if connection in connections: return True else: logger.error("Connection {} is not compatible with lab %s", connection) return False self.validate_exprs.append(connection_present)
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)
def insertInstrument(self, instrument): """ Inserts instrument in labstate. Args: instrument (Instrument): instrument to insert. Raises: RuntimeError: Raised if duplicate names are found. TypeError: Raised if instrument is not of type :class:`~lightlab.laboratory.instruments.bases.Instrument` """ self.instruments.append(instrument) if instrument.bench and instrument.bench not in self.benches: logger.warning(f"Insterting *new* bench {instrument.bench.name}") self.benches.append(instrument.bench) if instrument.host and instrument.host not in self.hosts: logger.warning(f"Inserting *new* host {instrument.host.name}") self.hosts.append(instrument.host)
def setPlotOptions(self, **kwargs): ''' Valid options for NdSweeper * plType * xKey * yKey * axArr * cmap-surf * cmap-curves Valid options for CommandControlSweeper * plType ''' for k, v in kwargs.items(): if k not in self.plotOptions.keys(): logger.warning(k, '%s is not a valid plot option.') logger.warning('Valid ones are %s', self.plotOptions.keys()) else: self.plotOptions[k] = v return self.plotOptions
def init_module(module): # do something that imports this module again empty_lab = False try: module.lab = module.LabState.loadState(_filename) except (OSError) as e: logger.error("%s: %s.", e.__class__.__name__, e) empty_lab = True except JSONDecodeError as e: if os.stat(_filename).st_size == 0: logger.warning("%s is empty.", _filename) else: logger.error("%s: %s is corrupted. %s.", e.__class__.__name__, _filename, e) empty_lab = True if empty_lab: logger.warning("Starting fresh new LabState(). " "Save for the first time with lab._saveState()") module.lab = module.LabState()
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)
def _triggerAcquire(self, timeout=None): ''' Sends a signal to the scope to wait for a trigger event. Waits until acquisition completes or timeout (in seconds). If timeout is very long, it will try a test first ''' if timeout is None: timeout = self.timeout / 1e3 if timeout > 60: logger.warning(f'Long timeout {timeout} specified, testing') old_avgCnt = self.timebaseConfig()['avgCnt'] self.timebaseConfig(avgCnt=2) self._triggerAcquire(timeout=10) logger.warning('Test succeeded. Doing long average now') self.timebaseConfig(avgCnt=old_avgCnt) if self._clearBeforeAcquire: self.write('ACQUIRE:DATA:CLEAR') # clear out average history self.write('ACQUIRE:STATE 1') # activate the trigger listener # Bus and entire program stall until acquisition completes. Maximum of 30 seconds self.wait(int(timeout * 1e3))
def enforceRange(cls, val, mode): ''' Returns clipped value. Raises RangeError ''' bnds = [cls.baseUnit2val(vBnd, mode) for vBnd in cls.baseUnitBounds] enforcedValue = np.clip(val, *bnds) if enforcedValue != val: logger.warning( 'Warning: value out of range was constrained.\n' 'Requested %s.' 'Allowed range is %s in %s s.', val, bnds, mode) if cls.exceptOnRangeError: if val < min(bnds): violation_direction = 'low' elif val > max(bnds): violation_direction = 'high' else: violation_direction = None raise RangeError('Current sources requested out of range.', violation_direction) return enforcedValue
def _reparse(self, parseKeys=None): ''' Reprocess measured data into parsed data. If there is not enough data present, it does nothing. If the parser depends on Args: parseKeys (tuple, str, None): which parsers to recalculate. If None, does all. Execution order depends on addParser calls, not parseKeys Returns: None ''' if self.data is None: return if parseKeys is None: parseKeys = tuple(self.parse.keys()) else: parseKeys = argFlatten(parseKeys, typs=tuple) for pk, pFun in self.parse.items( ): # We're indexing this way to make sure parsing is done in the order of parse attribute, not the order of parseKeys if pk not in parseKeys: continue tempDataMat = np.zeros(self.swpShape) for index in np.ndindex(self.swpShape): dataOfPt = OrderedDict() for datKey, datVal in self.data.items(): if np.any(datVal.shape != self.swpShape): logger.warning( 'Data member %s is wrong size for reparsing %s. Skipping.', datKey, pk) else: dataOfPt[datKey] = datVal[index] try: tempDataMat[index] = pFun(dataOfPt) except KeyError: logger.warning( 'Parser %s depends on unpresent data. Skipping.', pk) break else: self.data[pk] = tempDataMat
def __getstate__(self): ''' This method removes all variables in ``notPickled`` during serialization. ''' state = super().__getstate__() allNotPickled = self.notPickled for base in type(self).mro(): try: theirNotPickled = base.notPickled allNotPickled = allNotPickled.union(theirNotPickled) except AttributeError: pass keys_to_delete = set() for key, val in state.copy().items(): if isinstance(key, str): # 1. explicit removals if key in allNotPickled: keys_to_delete.add(key) # 2. hardware placeholders elif (val.__class__.__name__ == 'VISAObject' or any(base.__name__ == 'VISAObject' for base in val.__class__.mro())): klassname = val.__class__.__name__ logger.warning('Not pickling %s = %s.', key, klassname) state[key] = HardwareReference('Reference to a ' + klassname) # 3. functions that are not available in modules - saves the code text elif jsonpickle.util.is_function( val) and not jsonpickle.util.is_module_function(val): state[key + '_dilled'] = dill.dumps(val) keys_to_delete.add(key) # 4. double underscore attributes have already been removed for key in keys_to_delete: del state[key] return state
def setMonitorOptions(self, **kwargs): ''' Valid options for NdSweeper * livePlot * plotEvery * stdoutPrint * runServer Valid options for CommandControlSweeper * livePlot * plotEvery * stdoutPrint * runServer * cmdCtrlPrint ''' for k, v in kwargs.items(): if k not in self.monitorOptions.keys(): logger.warning(k, '%s is not a valid monitor option.') logger.warning('Valid ones are %s', self.monitorOptions.keys()) else: self.monitorOptions[k] = v return self.monitorOptions
def interpInverse(xArrIn, yArrIn, startIndex, direction, threshVal): ''' Gives a float representing the interpolated x value that gives y=threshVal ''' xArr = xArrIn.copy() yArr = yArrIn.copy() if direction == 'left': xArr = xArr[::-1] yArr = yArr[::-1] startIndex = len(xArr) - 1 - startIndex xArr = xArr[startIndex:] yArr = yArr[startIndex:] yArr = yArr - threshVal possibleRange = (np.min(yArrIn), np.max(yArrIn)) # warnStr = 'Inversion requested y = {}, but {} of range is {}' if threshVal < possibleRange[0]: logger.warning('Inversion requested y = %s, but %s of range is %s', threshVal, 'minimum', np.min(yArrIn)) return xArr[-1] elif threshVal > possibleRange[1]: logger.warning('Inversion requested y = %s, but %s of range is %s', threshVal, 'maximum', np.max(yArrIn)) return xArr[0] fakeInvalidIndeces = np.zeros(len(yArr), dtype=bool) iHit, isValid = descend(yArr, fakeInvalidIndeces, startIndex=0, direction='right', threshVal=0) if not isValid: logger.warning('Did not descend across threshold. Returning minimum') return xArr[np.argmin(yArr)] elif iHit in [0]: return xArr[iHit] else: # interpolate q = yArr[iHit - 1:iHit + 1][::-1] v = xArr[iHit - 1:iHit + 1][::-1] return np.interp(0, q, v)