def __init__(self, name, role, sn=None, max_power=0.1, inversed=False, **kwargs): """ sn (None or str): serial number. If None, it will pick the first device found. max_power (0<float): maxium power emitted in W. """ model.Emitter.__init__(self, name, role, **kwargs) self._sn = sn self._max_power = max_power # Just find the first BlinkStick led controller if sn is None: self._bstick = blinkstick.find_first() else: # Note: doesn't work with v1.1.7: # need fix on get_string(), reported here: https://github.com/arvydas/blinkstick-python/pull/35 logging.warning( "Using sn to select the device doesn't currently work") self._bstick = blinkstick.find_by_serial(sn) if self._bstick is None: raise HwError( "Failed to find a Blinkstick for component %s. " "Check that the device is connected to the computer." % (name, )) self._bstick.set_inverse(inversed) time.sleep(0.1) # Device apparently needs some time to recover self._shape = () # list of 5-tuples of floats self.spectra = model.ListVA([(380e-9, 390e-9, 560e-9, 730e-9, 740e-9)], unit="m", readonly=True) self.power = model.ListContinuous([ 0., ], ((0., ), (max_power, )), unit="W", cls=(int, long, float), setter=self._setPower) self.power.subscribe(self._updatePower, init=True) self._swVersion = "Blinkstick v%s" % (blinkstick.__version__, ) # These functions report wrong values on Linux with v1.1.7 # man = self._bstick.get_manufacturer() # desc = self._bstick.get_description() # rsn = self._bstick.get_serial() man = self._bstick.device.manufacturer desc = self._bstick.device.product rsn = self._bstick.device.serial_number self._hwVersion = "%s %s (s/n: %s)" % (man, desc, rsn)
def __init__(self, name, role, **kwargs): # TODO: allow the user to indicate the power and the spectrum via args? # This will create the .powerSupply VA model.Emitter.__init__(self, name, role, **kwargs) self.powerSupply.value = False # immediately turn it off self._shape = () self.power = model.ListContinuous([0], ((0,), (10,)), unit="W", cls=(int, long, float), setter=self._setPower) # just one band: white # TODO: update spectra VA to support the actual spectra of the lamp self.spectra = model.ListVA([(380e-9, 390e-9, 560e-9, 730e-9, 740e-9)], unit="m", readonly=True)
def __init__(self, name, role, max_power=10.0, spectra=None, **kwargs): """ max_power (0 < float): the maximum power (in W) spectra (list of list of 5 tuple): output spectrum, as 5 wavelengths in m """ model.Emitter.__init__(self, name, role, **kwargs) self._shape = () self.power = model.ListContinuous([0], ((0,), (max_power,)), unit="W", cls=(int, long, float), setter=self._setPower) # just one band: white # list of 5-tuples of floats if spectra is None: spectra = [(380e-9, 390e-9, 560e-9, 730e-9, 740e-9)] # White if len(spectra) != 1 or len(spectra[0]) != 5: raise ValueError("spectra argument must be a list of list of 5 values") self.spectra = model.ListVA([tuple(spectra[0])], unit="m", readonly=True)
def test_lc(self): """ Test ListContinuous behavior """ va = model.ListContinuous([0.1, 10, .5], ((-1.3, 9, 0), (100., 150., 1.)), cls=(int, long, float)) self.assertEqual(va.value, [0.1, 10, .5]) self.assertEqual(va.range, ((-1.3, 9, 0), (100., 150., 1.))) # must convert anything to a list va.value = (-1, 150, .5) self.assertEqual(va.value, [-1, 150, .5]) # must not accept values outside of the range try: va.value[1] = 160. self.fail("Assigning value not in range should not be allowed.") except IndexError: pass # as it should be
def __init__(self, name, role, dependencies, **kwargs): """ dependencies (dict str -> Emitter): arbitrary role -> emitter to be used as part of this emitter. All its provided emissions will be provided. """ # TODO: allow to only use a subset of the emissions from each child if not dependencies: raise ValueError("MultiplexLight needs dependencies") model.Emitter.__init__(self, name, role, dependencies=dependencies, **kwargs) self._shape = () self._child_idx = {} # Emitter -> index (shift) in the power/spectra spectra = [] min_power = [] max_power = [] for n, child in dependencies.items(): if not (model.hasVA(child, "power") and model.hasVA(child, "spectra") ): raise ValueError("Child %s is not a light emitter" % (n,)) self._child_idx[child] = len(spectra) spectra.extend(child.spectra.value) min_power.extend(child.power.range[0]) max_power.extend(child.power.range[1]) # Subscribe to each child power to update self.power child.power.subscribe(self._updateMultiplexPower) # Child with the maximum power range self.power = model.ListContinuous(value=[0] * len(spectra), range=(tuple(min_power), tuple(max_power)), unit="W", cls=(int, long, float)) self.power.subscribe(self._setChildPower) self._updateMultiplexPower(None) # info on which source is which wavelength self.spectra = model.ListVA(spectra, unit="m", readonly=True)
def __init__(self, name, role, device, channels, spectra, pwr_curve, **kwargs): """ device (string): name of the /dev comedi device (ex: "/dev/comedi0") channels (list of (0<=int)): The output channel for each source, as numbered in the comedi subdevice. spectra (list of 5-tuple of float): the spectra for each output channel used. Each tuple represents the wavelength in m for the 99% low, 25% low, centre/max, 25% high, 99% high. They do no have to be extremely precise. The most important is the centre, and that they are all increasing values. pwr_curve (list of dict (float -> 0<float)): Power curve segment map for each source. A segment map is a series of voltage output on the analog output -> emission power of the light (W). It represents a series of linear segments to map the voltage output to the light emission. At least one pair should be provided. If no voltage is linked to 0W, then a 0V -> 0W mapping is used. The total curve should be monotonic. """ # TODO: allow to give the unit of the power/pwr_curve ? model.Emitter.__init__(self, name, role, **kwargs) self._shape = () try: self._device = comedi.open(device) # self._fileno = comedi.fileno(self._device) except comedi.ComediError: raise ValueError("Failed to open DAQ device '%s'" % device) # Look for the analog output subdevice try: self._ao_subd = comedi.find_subdevice_by_type( self._device, comedi.SUBD_AO, 0) nchan = comedi.get_n_channels(self._device, self._ao_subd) if nchan < max(channels): raise ValueError( "Device only has %d channels, while needed %d" % (nchan, max(channels))) except comedi.ComediError: raise ValueError( "Failed to find an analogue output on DAQ device '%s'" % device) if len(channels) != len(spectra): raise ValueError( "spectra argument should have the same length as channels (%d)" % len(channels)) if len(channels) != len(pwr_curve): raise ValueError( "pwr_curve argument should have the same length as channels (%d)" % len(channels)) self._channels = channels # Check and store the power curves self._ranges = [] self._pwr_curve = [] for c, crv in zip(channels, pwr_curve): crv = [v for v in crv.items()] # Add 0W = 0V if nothing = 0W if 0 not in [w for v, w in crv]: crv.append((0, 0)) logging.info( "Adding 0V -> 0W mapping to pwr_curve for channel %d", c) # At least beginning and end values if len(crv) < 2: raise ValueError( "pwr_curve for channel %d has less than 2 values: %s" % (c, crv)) # Check it's monotonic crv = sorted(crv, key=lambda v: v[0]) if crv[0][1] < 0: raise ValueError( "pwr_curve for channel %d has negative power: %g W" % (c, crv[0][1])) if len(crv) != len(set(v for v, w in crv)): raise ValueError( "pwr_curve for channel %d has identical voltages: %s" % (c, crv)) if not all( (crv[i][1] < crv[i + 1][1]) for i in range(len(crv) - 1)): raise ValueError( "pwr_curve for channel %d is not monotonic: %s" % (c, crv)) self._pwr_curve.append(crv) # Find the best range to use try: ri = comedi.find_range(self._device, self._ao_subd, c, comedi.UNIT_volt, crv[0][0], crv[-1][0]) except comedi.ComediError: raise ValueError( "Data range between %g and %g V is too high for hardware." % (crv[0][0], crv[-1][0])) self._ranges.append(ri) # Check the spectra spect = [] # list of the 5 wavelength points for c, wls in zip(channels, spectra): if len(wls) != 5: raise ValueError( "Spectra for channel %d doesn't have exactly 5 wavelength points: %s" % (c, wls)) if list(wls) != sorted(wls): raise ValueError( "Spectra for channel %d has unsorted wavelengths: %s" % (c, wls)) for wl in wls: if not 0 < wl < 100e-6: raise ValueError( "Spectra for channel %d has unexpected wavelength = %f nm" % (c, wl * 1e9)) spect.append(tuple(wls)) # Maximum power for channel to be used as a range for power max_power = tuple([crv[-1][1] for crv in self._pwr_curve]) # Power value for each channel of the device self.power = model.ListContinuous( value=[0.] * len(self._channels), range=( tuple([0.] * len(self._channels)), max_power, ), unit="W", cls=(int, long, float), ) self.power.subscribe(self._updatePower) # info on which channel is which wavelength self.spectra = model.ListVA(spect, unit="m", readonly=True) # make sure everything is off (turning on the HUB will turn on the lights) self.power.value = self.power.range[0] self._metadata = {model.MD_HW_NAME: self.getHwName()} lnx_ver = driver.get_linux_version() self._swVersion = "%s (driver %s, linux %s)" % ( odemis.__version__, self.getSwVersion(), ".".join( "%s" % v for v in lnx_ver)) self._metadata[model.MD_SW_VERSION] = self._swVersion self._metadata[model.MD_HW_VERSION] = self._hwVersion # unknown
def __init__(self, name, role, port, sources, _serial=None, **kwargs): """ port (string): name of the serial port to connect to. Can be a pattern, in which case, all the ports fitting the pattern will be tried, and the first one which looks like an LLE will be used. sources (dict string -> 5-tuple of float): the light sources (by colour). The string is one of the seven names for the sources: "red", "cyan", "green", "UV", "yellow", "blue", "teal". They correspond to fix number in the LLE (cf documentation). The tuple contains the wavelength in m for the 99% low, 25% low, centre/max, 25% high, 99% high. They do no have to be extremely precise. The most important is the centre, and that they are all increasing values. If the device doesn't have the source it can be skipped. _serial (serial): for internal use only, directly use a opened serial connection """ # start with this opening the port: if it fails, we are done if _serial is not None: self._try_recover = False self._serial = _serial self._port = "" else: self._serial, self._port = self._findDevice(port) logging.info("Found LLE device on port %s", self._port) self._try_recover = True # to acquire before sending anything on the serial port self._ser_access = threading.Lock() # Init the LLE self._initDevice() if _serial is not None: # used for port testing => only simple init return # parse source and do some sanity check if not sources or not isinstance(sources, dict): logging.error( "sources argument must be a dict of source name -> wavelength 5 points" ) raise ValueError("Incorrect sources argument") self._source_id = [] # source number for each spectra self._gy = [] # indexes of green and yellow source self._rcubt = [] # indexes of other sources spectra = [] # list of the 5 wavelength points self._max_power = [] for cn, wls in sources.items(): cn = cn.lower() if cn not in COLOUR_TO_SOURCE: raise ValueError( "Sources argument contains unknown colour '%s'" % cn) if len(wls) != 5: raise ValueError( "Sources colour '%s' doesn't have exactly 5 wavelength points" % cn) prev_wl = 0 for wl in wls: if 0 > wl or wl > 100e-6: raise ValueError( "Sources colour '%s' has unexpected wavelength = %f nm" % (cn, wl * 1e9)) if prev_wl > wl: raise ValueError( "Sources colour '%s' has unsorted wavelengths" % cn) self._source_id.append(COLOUR_TO_SOURCE[cn]) if cn in ["green", "yellow"]: self._gy.append(len(spectra)) else: self._rcubt.append(len(spectra)) self._max_power.append(DEFAULT_SOURCES_POWERS[cn]) spectra.append(tuple(wls)) model.Emitter.__init__(self, name, role, **kwargs) self._shape = () self.power = model.ListContinuous( value=[0.0] * len(spectra), range=( (0., ) * len(spectra), tuple(self._max_power), ), unit="W", cls=(int, long, float), ) self.spectra = model.ListVA(spectra, unit="m", readonly=True) self._prev_power = [None] * len(spectra) # => will update for sure self._updateIntensities() # turn off every source self.power.subscribe(self._updatePower) # set HW and SW version self._swVersion = "%s (serial driver: %s)" % ( odemis.__version__, driver.getSerialDriver(self._port)) self._hwVersion = "Lumencor Light Engine" # hardware doesn't report any version # Update temperature every 10s current_temp = self.GetTemperature() self.temperature = model.FloatVA(current_temp, unit=u"°C", readonly=True) self._temp_timer = util.RepeatingTimer(10, self._updateTemperature, "LLE temperature update") self._temp_timer.start()