Beispiel #1
0
class AGC_100:
    def __init__(self, port, logger=None):
        """Instantiates serial connection to pressure gauge
        """
        # Instantiate log
        self.log = LogHandler(logger=logger)

        ser = serial.Serial(port=port,
                            baudrate=9600,
                            timeout=1,
                            parity=serial.PARITY_NONE,
                            stopbits=serial.STOPBITS_ONE,
                            bytesize=serial.EIGHTBITS)

        self.sio = io.TextIOWrapper(io.BufferedRWPair(ser, ser))

        # Log initial pressure.
        init_pressure = self.get_pressure()
        self.log.info(f"Successfully reading {init_pressure} mBar.")

    def get_pressure(self):
        '''Returns pressure in mBar'''
        raw_data = self.sio.readline()
        pressure = float(raw_data.split(",")[1].replace(" mbar", ""))
        return pressure
Beispiel #2
0
class Driver():
    """Driver class"""

    def reset(self):
        """ Create factory reset"""
        self.device.write('*RST')
        self.log.info("Reset to factory settings successfull.")

    def __init__(self, gpib_address, logger):
        """Instantiate driver class

        :gpib_address: GPIB-address of spectrum analyzer, e.g. 'GPIB0::12::INSTR'
            Can be read out by using
                rm = pyvisa.ResourceManager()
                rm.list_resources()
        :logger: And instance of a LogClient
        """

        # Instantiate log
        self.log = LogHandler(logger=logger)

        self.rm = ResourceManager()

        try:
            self.device = self.rm.open_resource(gpib_address)
            device_id = self.device.query('*IDN?')
            self.log.info(f"Successfully connected to {device_id}.")
        except VisaIOError:
            self.log.error(f"Connection to {gpib_address} failed.")

        # reset to factory settings
        self.reset()

    def turn_laser_off(self):
        """ Power off display """
        self.device.write('OUTPut1:STATe 0') # temp control
        self.device.write('OUTPut2:STATe 0') # diode
        self.log.info("Laser off.")

    def turn_laser_on(self):
        """ Power on display """
        self.device.write('OUTPut2:STATe 1') # temp control
        self.device.write('OUTPut1:STATe 1') # diode
        self.log.info("Laser on.")

    def set_current(self, amps):
        """ Sets current setpoint in mA
        :amps:
        """

        if not 0 <= amps <= 60:
            self.log.error(
                f'Invalid current ({amps}mA). Attenuation must be between 0A and 60mA'
            )
        self.device.write(f'SOURce1:CURRent:LEVel:AMPLitude {amps*1e-3}')
        self.log.info(f'Current setpoint set to {amps}mA.')
Beispiel #3
0
class Wrap:
    """ Wrapper for the full hardware interface to SITT.

    Can initialize multiple measurements with a single device/ same set of channels.
    Desired workflow:

    counter = Wrap(tagger, logger)
    counter.set_ch_assignment(name='count_trace', ch_list=[1,2,3])
    counter.start_ctr(name='count_trace')

    counter.set_ch_assignment(name='rate_monitor', ch_list=[1])
    counter.init_rate_monitor(name='rate_monitor')
    """
    def __init__(self, tagger, logger=None):
        """Instantiate count monitor

        :param tagger: instance of TimeTagger class
        :param ch_list: list of channels to count
        :param logger: instance of LogClient class, optional
        """

        # Log
        self.log = LogHandler(logger=logger)

        # Reference to tagger
        self._tagger = tagger

        # Log device ID information to demonstrate that connection indeed works
        serial = self._tagger.getSerial()
        model = self._tagger.getModel()
        self.log.info(
            'Got reference to Swabian Instruments TimeTagger device \n'
            'Serial number: {0}, Model: {1}'
            ''.format(serial, model))

        # Counter instance
        self._ctr = {}
        self._channels = {}

    def start_trace(self,
                    name=None,
                    ch_list=[1],
                    bin_width=1000000000,
                    n_bins=10000):
        """Start counter - used for count-trace applications

        :param name: (str) identifier for the counter measurement
        :param ch_list: (list) list of channels to count
        :param bin_width: integer in ps for width of count bins
        :param n_bins: integer number of bins to store before
                            wrapping around
        """

        # If a name is not provided just use the index of the measurement
        if name is None:
            name = str(len(self._ctr))

        # Instantiate Counter instance, see TT documentation
        self._ctr[name] = TT.Counter(self._tagger,
                                     channels=ch_list,
                                     binwidth=bin_width,
                                     n_values=n_bins)

        self.log.info('Set up count trace measurement on channel(s)'
                      f' {ch_list}')

    def clear_ctr(self, name=None):
        """Resets the array to zero and restarts the measurement.
        Generic method for most measurement types.
        See the clear() method of Counter class in TT

        :param name: (str) identifier for the counter measurement
        """

        name = self.handle_name(name)

        # Clear counter (see TT documentation)
        self._ctr[name].clear()

    def get_counts(self, name=None):
        """Gets a 2D array of counts on all channels. See the
            getData() method of Counter class in TT

        :param name: (str) identifier for the counter measurement
        """

        name = self.handle_name(name)

        # Get count data (see TT documentation)
        return self._ctr[name].getData()

    def get_x_axis(self, name=None):
        """Gets the x axis in picoseconds for the count array.
            See the getIndex() method of Counter class in TT

        :param name: (str) identifier for the counter measurement
        """

        name = self.handle_name(name)

        # Get x axis
        return self._ctr[name].getIndex()

    def start_rate_monitor(self, name=None, ch_list=[1]):
        """Sets up a measurement for count rates

        :param name: (str) identifier for the counter
        :param ch_list: (list) list of channels to measure
        """

        # If a name is not provided just use the index of the measurement
        if name is None:
            name = str(len(self._ctr))

        ch_list = [self._get_channel(ch) for ch in ch_list]

        # Instantiate Counter instance, see TT documentation
        self._ctr[name] = TT.Countrate(self._tagger, channels=ch_list)

        self.log.info('Set up count rate measurement on channel(s)'
                      f' {ch_list}')

    def get_count_rate(self, name=None, ctr_index=0, integration=0.1):
        """ Reports the current count rate

        :param name: (str) name of counter to use
        :param ctr_index: (int) index of counter to get data for
        :param integration: (float) roughly how long to measure for
        """

        self.clear_ctr(name=name)
        time.sleep(integration)
        return self._ctr[name].getData()

    def start_gated_counter(self,
                            name,
                            click_ch,
                            gate_ch,
                            gated=True,
                            bins=1000):
        """ Starts a new gated counter

        :param name: (str) name of counter measurement to use
        :param click_ch: (int) click channel number -8...-1, 1...8
        :param gate_ch: (int) gate channel number -8...-1, 1...8
        :param gated: (bool) whether or not to physicall gate, or just count between
            gate_ch edges
        :param bins: (int) number of bins (gate windows) to store
        """

        if gated:
            self._ctr[name] = TT.CountBetweenMarkers(
                self._tagger,
                self._get_channel(click_ch),
                self._get_channel(gate_ch),
                end_channel=-gate_ch,
                n_values=bins)
        else:
            self._ctr[name] = TT.CountBetweenMarkers(
                self._tagger,
                self._get_channel(click_ch),
                self._get_channel(gate_ch),
                n_values=bins)

    def start_histogram(self,
                        name,
                        start_ch,
                        click_ch,
                        next_ch=-134217728,
                        sync_ch=-134217728,
                        binwidth=1000,
                        n_bins=1000,
                        n_histograms=1,
                        start_delay=None):
        """ Sets up a Histogram measurement using the TT.TimeDifferences
        measurement class

        :param name: (str) name of measurement for future reference
        :param start_ch: (int) index of start channel -8...-1, 1...8
        :param click_ch: (int or str) index of counts channel -8...-1, 1...8
            if physical, otherwise channel name if virtual
        :param next_ch: (int, optional) channel used to mark transition
            to next histogram (for multi-channel histograms)
        :param sync_ch: (int, optional) channel used to mark reset of
            histogram index
        :param binwidth: (int) width of bin in ps
        :param n_bins: (int) number of bins for total measurement
        :param n_histograms: (int) total number of histograms
        :param start_delay: (optional, int) delay for marker in ps
        """

        if start_delay is not None:
            self._channels[name] = TT.DelayedChannel(
                tagger=self._tagger,
                input_channel=self._get_channel(start_ch),
                delay=start_delay)
            start_ch = name

        self._ctr[name] = TT.TimeDifferences(
            tagger=self._tagger,
            click_channel=self._get_channel(click_ch),
            start_channel=self._get_channel(start_ch),
            next_channel=next_ch,
            sync_channel=sync_ch,
            binwidth=binwidth,
            n_bins=n_bins,
            n_histograms=n_histograms)

    def start_correlation(self,
                          name,
                          ch_1,
                          ch_2,
                          binwidth=1000,
                          n_bins=1000,
                          delay=None):
        """ Sets up a correlation measurement using TT.Correlation measurement class

        :param name: (str) name of measurement for future reference
        :param ch_1: (int or str) index of first click channel -8...-1, 1...8
            if physical, otherwise channel name if virtual
        :param ch_2: (int or str) index of second click channel -8...-1, 1...8
            if physical, otherwise channel name if virtual
        :param binwidth: (int) width of bin in ps
        :param n_Bins: (int) number of bins for total measurement
        :param delay: (optional, int) delay for channel 1
        """

        if delay is not None:
            label = f'{name}_delayed'
            self._channels[label] = TT.DelayedChannel(
                tagger=self._tagger,
                input_channel=self._get_channel(ch_1),
                delay=int(delay))
            ch_1 = label

        self._ctr[name] = TT.Correlation(tagger=self._tagger,
                                         channel_1=self._get_channel(ch_1),
                                         channel_2=self._get_channel(ch_2),
                                         binwidth=binwidth,
                                         n_bins=n_bins)

    def start(self, name):
        """ Starts a measurement.

        Can be used to restart a measurement once it has been stopped
        :param name: (str) name of the measurement for identification
        """

        self._ctr[name].start()

    def stop(self, name):
        """ Stops a measurement.

        Can be used to stop a measurement
        :param name: (str) name of the measurement for identification
        """

        self._ctr[name].stop()

    def create_gated_channel(self,
                             channel_name,
                             click_ch,
                             gate_ch,
                             delay=None):
        """ Creates a virtual channel that is gated

        :param channel_name: (str) name of channel for future reference
        :param click_ch: (int) index of click channel -8...-1, 1...8
        :param gate_ch: (int) index of gate channel -8...-1, 1...8
            Assumes gate starts on rising edge (if positive) and ends
            on falling edge
        :param delay: (optional, float) amount to delay gate by
        """

        # Create a delayed channel for the gate if needed
        if delay is not None:
            self._channels[f'{channel_name}_delayed'] = TT.DelayedChannel(
                tagger=self._tagger, input_channel=gate_ch, delay=int(delay))
            self._channels[
                f'{channel_name}_delayed_falling'] = TT.DelayedChannel(
                    tagger=self._tagger,
                    input_channel=-gate_ch,
                    delay=int(delay))
            self.log.info(
                f'Created delayed gate {channel_name}_delayed for gate channel {gate_ch}'
            )
            gate_ch = self._get_channel(f'{channel_name}_delayed')
            gate_stop_ch = self._get_channel(f'{channel_name}_delayed_falling')
        else:
            gate_stop_ch = -gate_ch

        self._channels[channel_name] = TT.GatedChannel(
            tagger=self._tagger,
            input_channel=self._get_channel(click_ch),
            gate_start_channel=self._get_channel(gate_ch),
            gate_stop_channel=self._get_channel(gate_stop_ch))
        self.log.info(f'Created gated channel {channel_name}, '
                      f'click channel: {click_ch}, gate channel: {gate_ch}')

    def create_delayed_channel(self, channel_name, click_ch, delay):
        """ Creates a delayed virtual channel allowing for a user to input a delay
        :param channel_name: (str) name, identifier of the channel
        :param click_ch: (int) index of  channel
        :param delay: (optional, int) amount to delay by
        """
        self._channels[channel_name] = TT.DelayedChannel(
            tagger=self._tagger,
            input_channel=self._get_channel(click_ch),
            delay=int(delay))
        self.log.info(f'Created delayed channel {channel_name}, '
                      f'click channel: {click_ch}, delay: {delay}')

    def create_combined_channel(self, channel_name, channel_list):
        """ Creates a combined virtual channel which includes events from multiple cahnnels

        :param channel_name: (str) name, identifier of the channel
        :param channel_list: (list) list of channel numbers or names to combine
        """

        # Handle virtual channels in channel_list
        channels = [self._get_channel(ch) for ch in channel_list]
        self._channels[channel_name] = TT.Combiner(tagger=self._tagger,
                                                   channels=channels)

    def update_delay(self, channel_name, delay):
        """ Updates the delay for a gated + delayed channel

        :param channel_name: (str) identifier name of gated channel
        :param delay: (float) value of delay to update to in ps
        """

        self._channels[f'{channel_name}_delayed'].setDelay(int(delay))
        self._channels[f'{channel_name}_delayed_falling'].setDelay(int(delay))
        self.log.info(f'Updated {channel_name} delay to {delay}')

    def _get_channel(self, ch):
        """ Handle virtual channel input

        :param ch: (int or str) channel index (if physical) or name (virtual)

        :return: (int) channel number of physical or virtual channel
        """

        if isinstance(ch, str):
            return self._channels[ch].getChannel()
        else:
            return ch

    def set_trigger_level(self, channel, voltage):
        """ Set the trigger level of physics channel

        :param ch: (int) channel index of physical channel

        :param voltage: (float) Trigger level in volts.
        """

        self._tagger.setTriggerLevel(int(channel), float(voltage))
        new_trigger_val = self._tagger.getTriggerLevel(int(channel))
        self.log.info(
            f"Changed trigger level of channel {channel} to {new_trigger_val} V."
        )

    @staticmethod
    def handle_name(name):
        if name is None:
            return '0'
        else:
            return name
Beispiel #4
0
class WlmMonitor:
    """ A script class for monitoring and locking lasers based on the wavemeter """
    def __init__(self,
                 wlm_client,
                 logger_client,
                 gui='wavemeter_monitor',
                 ao_clients=None,
                 display_pts=5000,
                 threshold=0.0002,
                 port=None,
                 params=None,
                 three_lasers=False):
        """ Instantiates WlmMonitor script object for monitoring wavemeter

        :param wlm_client: (obj) instance of wavemeter client
        :param gui_client: (obj) instance of GUI client.
        :param logger_client: (obj) instance of logger client.
        :param ao_clients: (dict, optional) dictionary of ao client objects with keys to identify. Exmaple:
            {'ni_usb_1': nidaqmx_usb_client_1, 'ni_usb_2': nidaqmx_usb_client_2, 'ni_pxi_multi': nidaqmx_pxi_client}
        :param display_pts: (int, optional) number of points to display on plot
        :param threshold: (float, optional) threshold in THz for lock error signal
        :param port: (int) port number for update server
        :param params: (dict) see set_parameters below for details
        :three_lasers: (bool) If three lasers are in use (instead of 2)
        """
        self.channels = []

        if three_lasers:
            gui = 'wavemeter_monitor_3lasers'

        self.wlm_client = wlm_client
        self.ao_clients = ao_clients
        self.display_pts = display_pts
        self.threshold = threshold
        self.log = LogHandler(logger_client)

        # Instantiate gui
        self.gui = Window(
            gui_template=gui,
            host=get_ip(),
            port=port,
        )

        # Setup stylesheet.
        self.gui.apply_stylesheet()

        if three_lasers:
            self.widgets = get_gui_widgets(gui=self.gui,
                                           freq=3,
                                           sp=3,
                                           rs=3,
                                           lock=3,
                                           error_status=3,
                                           graph=6,
                                           legend=6,
                                           clear=6,
                                           zero=6,
                                           voltage=3,
                                           error=3)
        else:
            self.widgets = get_gui_widgets(gui=self.gui,
                                           freq=2,
                                           sp=2,
                                           rs=2,
                                           lock=2,
                                           error_status=2,
                                           graph=4,
                                           legend=4,
                                           clear=4,
                                           zero=4,
                                           voltage=2,
                                           error=2)

        # Set parameters
        self.set_parameters(**params)

        # Configure plots
        # Get actual legend widgets
        self.widgets['legend'] = [
            get_legend_from_graphics_view(legend)
            for legend in self.widgets['legend']
        ]

        self.widgets['curve'] = []
        self.initialize_channels()

        for channel in self.channels:
            self.update_parameters(
                dict(channel=channel.number, setpoint=channel.data[-1]))

    def set_parameters(self, channel_params):
        """ Instantiates new channel objects with given parameters and assigns them to the WlmMonitor

        Note: parameters for a channel that has already been assigned can be set or updated later using the
        update_parameters() method via an update client in a separate python process.

        :param channel_params: (list) of dictionaries containing all parameters. Example of full parameter set:
            {'channel': 1, 'name': 'Velocity', 'setpoint': 406.7, 'lock':True, 'memory': 20,
             'pid': {'p': 1, 'i': 0.1, 'd': 0}, 'ao': {'client':'nidaqmx_client', 'channel': 'ao1'}}

            In more detail:
            - 'channel': should be from 1-8 for the High-Finesse Wavemeter (with switch) and should ALWAYS be provided,
                as a reference so that we know which channel to assign all the other parameters to
            - 'name': a string that can just be provided once and is used as a user-friendly name for the channel.
                Initializes to 'Channel X' where X is a random integer if not provided
            - 'setpoint': setpoint for this channel.
            - 'lock': boolean that tells us whether or not to turn on the lock. Ignored if setpoint is None. Default is
                False.
            - 'memory': Number of points for integral memory of pid (history of the integral). Default is 20.
            - 'pid': dict containing pid parameters. Uses the pylabnet.scripts.pid module. By default instantiates the
                default PID() object.
            - 'ao': dict containing two elements: 'client' which is a string that is the name of the ao client to use
                for locking. This should match up with a key in self.ao_clients. 'channel'is an identifier for which
                analog output to use for this channel. By default it is set to None and no active locking is performed
        """

        # Check if it is only a single channel
        if type(channel_params) is dict:
            channel_params = [channel_params]

        # Initialize each channel individually
        for channel_param_set in channel_params:
            self.channels.append(
                Channel(channel_param_set, self.ao_clients, log=self.log))

    def update_parameters(self, parameters):
        """ Updates only the parameters given. Can be used in the middle of the script operation via an update client.

        :param parameters: (list) list of dictionaries, see set_parameters() for details
        """

        if not isinstance(parameters, list):
            parameters = [parameters]

        for parameter in parameters:

            # Make sure a channel is given
            if 'channel' in parameter:

                # Check if it is a channel that is already logged
                channel_list = self._get_channels()
                if parameter['channel'] in channel_list:

                    # Find index of the desired channel
                    index = channel_list.index(parameter['channel'])
                    channel = self.channels[channel_list.index(
                        parameter['channel'])]

                    # Set all other parameters for this channel
                    if 'name' in parameter:
                        channel.name = parameter['name']

                    if 'setpoint' in parameter:

                        # self.widgets['sp'][index].setValue(parameter['setpoint'])
                        # channel.setpoint = parameter['setpoint']

                        # Mark that we should override GUI setpoint, since it has been updated by the script
                        channel.setpoint_override = parameter['setpoint']

                    if 'lock' in parameter:

                        self.widgets['lock'][index].setChecked(
                            parameter['lock'])
                        # channel.lock = parameter['lock']

                        # Mark that we should override the GUI lock since it has been updated by the script
                        # channel.lock_override = True

                    if 'memory' in parameter:
                        channel.memory = parameter['memory']
                    if 'pid' in parameter:
                        channel.pid.set_parameters(
                            p=parameter['pid']['p'],
                            i=parameter['pid']['i'],
                            d=parameter['pid']['d'],
                        )

                    # Ignore ao requests if clients have not been assigned
                    if 'ao' in parameter and self.ao_clients is not None:

                        # Convert ao from string to object using lookup
                        try:
                            channel.ao = {
                                'client':
                                self.ao_clients[parameter['ao']['client']],
                                'channel':
                                parameter['ao']['channel']
                            }

                        # Handle case where the ao client does not exist
                        except KeyError:
                            channel.ao = None

                # Otherwise, it is a new channel so we should add it
                else:
                    self.channels.append(Channel(parameter))
                    self._initialize_channel(index=len(self.channels) - 1,
                                             channel=self.channels[-1])

    def initialize_channels(self):
        """Initializes all channels and outputs to the GUI"""

        for index, channel in enumerate(self.channels):
            self._initialize_channel(index, channel)

    def clear_channel(self, channel):
        """ Clears the plot output for this channel

        :param channel: Channel object to clear
        """

        try:
            channel.initialize(channel.data[-1])

        # If the channel isn't monitored
        except:
            self.log.warn('Could not clear channel')

    def clear_all(self):
        """ Clears all channels """

        for channel in self.channels:
            self.clear_channel(channel)

    def run(self):
        """Runs the WlmMonitor

        Can be stopped using the pause() method
        """

        self._get_gui_data()
        self._update_channels()
        self.gui.force_update()

    def zero_voltage(self, channel):
        """ Zeros the output voltage for this channel

        :param channel: Channel object to zero voltage of
        """

        try:
            channel.zero_voltage()
            self.log.info(f'Voltage centered for channel {channel.name}')

        # If the channel isn't monitored
        except:
            self.log.warn('Failed to zero voltage')

    def go_to(self, channel, value, step_size, hold_time):
        """ Sends laser to a setpoint value gradually

        :param channel: (int) channel number on wavemeter
        :param value: (float) value to set laser frequency to
        :param step_size: (float) step size in THz for laser freq steps
        :param hold_time: (float) time in seconds to wait between steps
        """

        # Index of channel
        physical_channel = self.channels[self._get_channels().index(channel)]

        # Generate array of points to go to
        traverse = np.linspace(
            physical_channel.setpoint, value,
            int((value - physical_channel.setpoint) / step_size))

        for frequency in traverse:
            self.set_parameters([dict(channel=channel, setpoint=frequency)])
            time.sleep(hold_time)

    # Technical methods

    def _initialize_channel(self, index, channel):
        """Initializes a channel and outputs to the GUI

        Should only be called in the beginning of channel use to assign physical GUI widgets
        """

        # Get wavelength and initialize data arrays
        channel.initialize(wavelength=self.wlm_client.get_wavelength(
            channel.number),
                           display_pts=self.display_pts)

        # Create curves
        # frequency
        self.widgets['curve'].append(self.widgets['graph'][2 * index].plot(
            pen=pg.mkPen(color=self.gui.COLOR_LIST[0])))
        add_to_legend(legend=self.widgets['legend'][2 * index],
                      curve=self.widgets['curve'][4 * index],
                      curve_name=channel.curve_name)

        # Setpoint
        self.widgets['curve'].append(self.widgets['graph'][2 * index].plot(
            pen=pg.mkPen(color=self.gui.COLOR_LIST[1])))
        add_to_legend(legend=self.widgets['legend'][2 * index],
                      curve=self.widgets['curve'][4 * index + 1],
                      curve_name=channel.setpoint_name)

        # Clear data
        self.widgets['clear'][2 * index].clicked.connect(
            lambda: self.clear_channel(channel))
        self.widgets['clear'][2 * index + 1].clicked.connect(
            lambda: self.clear_channel(channel))

        # Setpoint reset
        self.widgets['rs'][index].clicked.connect(
            lambda: self.update_parameters(
                dict(channel=channel.number, setpoint=channel.data[-1])))

        # Voltage
        self.widgets['curve'].append(self.widgets['graph'][2 * index + 1].plot(
            pen=pg.mkPen(color=self.gui.COLOR_LIST[0])))
        add_to_legend(legend=self.widgets['legend'][2 * index + 1],
                      curve=self.widgets['curve'][4 * index + 2],
                      curve_name=channel.voltage_curve)

        # Error
        self.widgets['curve'].append(self.widgets['graph'][2 * index + 1].plot(
            pen=pg.mkPen(color=self.gui.COLOR_LIST[1])))
        add_to_legend(legend=self.widgets['legend'][2 * index + 1],
                      curve=self.widgets['curve'][4 * index + 3],
                      curve_name=channel.error_curve)

        # zero
        self.widgets['zero'][2 * index].clicked.connect(
            lambda: self.zero_voltage(channel))
        self.widgets['zero'][2 * index + 1].clicked.connect(
            lambda: self.zero_voltage(channel))

    def _update_channels(self):
        """ Updates all channels + displays

        Called continuously inside run() method to refresh WLM data and output on GUI
        """

        for index, channel in enumerate(self.channels):

            # Check for override
            if channel.setpoint_override:
                self.widgets['sp'][index].setValue(channel.setpoint_override)
                channel.setpoint_override = 0

            # Update data with the new wavelength
            channel.update(self.wlm_client.get_wavelength(channel.number))

            # Update frequency
            self.widgets['curve'][4 * index].setData(channel.data)
            self.widgets['freq'][index].setValue(channel.data[-1])

            # Update setpoints
            self.widgets['curve'][4 * index + 1].setData(channel.sp_data)

            # Update the setpoint to GUI directly if it has been changed
            # if channel.setpoint_override:

            #     # Tell GUI to pull data provided by script and overwrite direct GUI input
            #     self.widgets['sp'][index].setValue(channel.setpoint)

            # If the lock has been updated, override the GUI
            # if channel.lock_override:
            #     self.widgets['lock'][index].setChecked(channel.lock)

            # Set the error boolean (true if the lock is active and we are outside the error threshold)
            if channel.lock and np.abs(channel.data[-1] -
                                       channel.setpoint) > self.threshold:
                self.widgets['error_status'][index].setChecked(True)
            else:
                self.widgets['error_status'][index].setChecked(False)

            # Now update lock + voltage plots
            self.widgets['curve'][4 * index + 2].setData(channel.voltage)
            self.widgets['voltage'][index].setValue(channel.voltage[-1])
            self.widgets['curve'][4 * index + 3].setData(channel.error)
            self.widgets['error'][index].setValue(channel.error[-1])

    def _get_gui_data(self):
        """ Updates setpoint and lock parameters with data pulled from GUI

        Does not overwrite the script setpoints and locks, but stores the GUI values for comparison based on context.
        See Channel.update() method for behavior on how script chooses whether to use internal values or GUI values
        """
        for index, channel in enumerate(self.channels):

            # Pull the current value from the GUI
            channel.gui_setpoint = self.widgets['sp'][index].value()
            channel.gui_lock = self.widgets['lock'][index].isChecked()

    def _get_channels(self):
        """ Returns all active channel numbers

        Usually used for checking whether a newly input channel has already been assigned to the script

        :return: (list) all active channel numbers
        """

        channel_list = []
        for channel in self.channels:
            channel_list.append(channel.number)
        return channel_list

    def get_wavelength(self, channel):
        # Index of channel
        physical_channel = self.channels[self._get_channels().index(channel)]
        return self.wlm_client.get_wavelength(physical_channel.number)
Beispiel #5
0
class Driver(WavemeterInterface):
    """ Hardware class to control High Finesse Wavemeter."""
    def __init__(self, logger=None):
        """ Instantiate wavemeter

        :param logger: instance of LogClient class (optional)
        """

        # Log
        self.log = LogHandler(logger=logger)

        # Load WLM DLL
        try:
            self._wavemeterdll = ctypes.windll.LoadLibrary('wlmData.dll')

        except:
            msg_str = 'High-Finesse WS7 Wavemeter is not properly installed on this computer'
            self.log.error(msg_str)
            raise WavemeterError(msg_str)

        # Set all DLL function parameters and return value types
        self._wavemeterdll.GetWLMVersion.restype = ctypes.c_long
        self._wavemeterdll.GetWLMVersion.argtype = ctypes.c_long

        self._wavemeterdll.GetWLMCount.restype = ctypes.c_long
        self._wavemeterdll.GetWLMCount.argtype = ctypes.c_long

        self._wavemeterdll.GetWavelengthNum.restype = ctypes.c_double
        self._wavemeterdll.GetWavelengthNum.argtypes = [
            ctypes.c_long, ctypes.c_double
        ]

        self._wavemeterdll.GetFrequencyNum.restype = ctypes.c_double
        self._wavemeterdll.GetFrequencyNum.argtypes = [
            ctypes.c_long, ctypes.c_double
        ]

        # Check that WLM is running and log details
        self._is_running = self._wavemeterdll.GetWLMCount(0)
        if self._is_running > 0:
            self._wlm_version = self._wavemeterdll.GetWLMVersion(0)
            self.log.info('Connected to High-Finesse Wavemeter WS-{0}'.format(
                self._wlm_version))

        else:
            msg_str = 'High-Finesse WS7 Wavemeter software not running.\n'
            'Please run the Wavemeter software and try again.'
            self.log.warn(msg_str)
            # raise WavemeterError(msg_str)

    def get_wavelength(self, channel=1, units='Frequency (THz)'):
        """ Returns the wavelength in specified units for a given channel

        :param channel: Channel number from 1-8
        :param units: "Frequency (THz)" or "Wavelength (nm)". Defaults to frequency.
        """

        if units == 'Wavelength (nm)':
            return self._wavemeterdll.GetWavelengthNum(channel, 0)

        else:
            return self._wavemeterdll.GetFrequencyNum(channel, 0)
Beispiel #6
0
class TimeTrace:
    """ Convenience class for handling time-trace measurements """

    def __init__(self, ctr: si_tt.Client, log: LogClient,
                 click_ch=1, start_ch=2, binwidth=1000, n_bins=1000, update_interval=0.5,
                 correlation=False, **kwargs):
        """ Instantiates TimeTrace measurement

        :param ctr: (si_tt.Client) client to timetagger hardware
        :param log: (LogClient) instance of logclient for logging
        :param **kwargs: additional keyword arguments including
            :param click_ch: (int) channel receiving clicks
            :param start_ch: (int) channel for starting histogram
            :param binwidth: (int) width of bins in ps
            :param n_bins: (int) total number of bins for histogram
            :param update_interval: (float) interval in seconds to wait between updates
                Note, don't go too small (< 100 ms, not precisely tested yet),
                otherwise we might lag in jupyter notebook
            :param correlation: (bool) whether or not this is correlation meas
            TODO: in future, can implement multiple histograms if useful
        """

        self.ctr = ctr
        self.log = LogHandler(log)

        # Store histogram parameters
        self.click_ch = click_ch
        self.start_ch = start_ch
        self.binwidth = binwidth
        self.n_bins = n_bins

        self.correlation = correlation
        if self.correlation:
            self.hist = f'correlation_{np.random.randint(1000)}'
        else:
            self.hist = f'histogram_{np.random.randint(1000)}'
        self.plot = None
        self.is_paused = False
        self.up_in = update_interval

    def start_acquisition(self):
        """ Begins time-trace acquisition """

        if self.correlation:
            self.ctr.start_correlation(
                name=self.hist,
                ch_1=self.start_ch,
                ch_2=self.click_ch,
                binwidth=self.binwidth,
                n_bins=self.n_bins
            )

        else:

            self.ctr.start_histogram(
                name=self.hist,
                start_ch=self.start_ch,
                click_ch=self.click_ch,
                binwidth=self.binwidth,
                n_bins=self.n_bins
            )

        self.log.info(f'Histogram counter {self.hist} started acquiring'
                      f' with click channel {self.click_ch} and start channel'
                      f' {self.start_ch}')

    def set_parameters(self, binwidth=1000, n_bins=1000, **kwargs):
        """ Updates histogram parameters

        :param binwidth: (int) width of bins in ps
        :param n_bins: (int) total number of bins for histogram
        """

        self.binwidth = binwidth
        self.n_bins = n_bins
        self.log.info('Set parameters for next histogram (will not affect '
                      'any histogram already in progress)')

    def init_plot(self):
        """ Instantiates a plot, assuming counter is live """
        self.plot = SingleTraceFig(title_str='Count Histogram')
        self.plot.set_data(
            x_ar=self.ctr.get_x_axis(self.hist) / 1e12,
            y_ar=self.ctr.get_counts(self.hist)[0]
        )

        self.plot.show()

    def _update_data(self):
        self.plot.set_data(
            y_ar=self.ctr.get_counts(self.hist)[0]
        )

    def go(self):
        """ Runs counter from scratch """

        self.start_acquisition()
        self.init_plot()

        self.is_paused = False
        while not self.is_paused:

            time.sleep(self.up_in)
            self._update_data()

    def resume(self):
        """ Runs an already instantiated counter."""

        self.is_paused = False
        while not self.is_paused:

            time.sleep(self.up_in)
            self._update_data()

    def clear(self):
        """ Clears the data """

        self.ctr.clear_ctr(name=self.hist)

        # self.log.info(f'Counter {self.hist} data cleared')

    def pause(self):
        """ Pauses the go/run loop.

        NOTE: does not actually stop counter acquisition!
        There does not seem to be a way to do that from SI-TT API
        """

        self.is_paused = True

    def save(self, filename=None, directory=None):
        """ Saves the current data """

        generic_save(
            data=np.array([
                self.ctr.get_x_axis(self.hist) / 1e12,
                self.ctr.get_counts(self.hist)[0]
            ]),
            filename=filename,
            directory=directory,
            date_dir=True
        )

        self.log.info('Saved histogram data')
Beispiel #7
0
class MCS2:

    # property keys and other constants
    PKEY_NUM_BUS = int("0x020F0016", 16)
    PKEY_NUM_CH = int('0x020F0017', 16)
    PKEY_NUM_BUS_CH = int('0x02030017', 16)
    PKEY_MOVE_MODE = int('0x03050087', 16)
    PKEY_STEP_FREQ = int('0x0305002E', 16)
    PKEY_STEP_AMP = int('0x03050030', 16)
    PKEY_VOLT = int('0x0305001F', 16)
    PKEY_DC_VEL = int('0x0305002A', 16)
    PKEY_CURRENT_STATE = int('0x0305000F', 16)
    MOVE_MODE_STEP = 4
    MOVE_MODE_DC_REL = 3
    MOVE_MODE_DC_ABS = 2
    SCALE = 65535
    DC_VEL_MIN = 100 / 56635
    DC_VEL_MAX = 10**8

    def __init__(self, logger=None):
        """ Instantiate Nanopositioners"""

        self.log = LogHandler(logger)
        self.dummy = False

        try:

            # Loads Nanopositioners DLL and define arguments and result types for c function
            self._nanopositionersdll = ctypes.windll.LoadLibrary(
                'SmarActCTL.dll')
            self._configure_functions()

            #Finds devices connected to controller
            buffer = ctypes.create_string_buffer(
                4096)  #the way to have a mutable buffer
            buffersize = ctypes.c_size_t(ctypes.sizeof(
                buffer))  #size _t gives c_ulonglong, not as in manual
            result = self._nanopositionersdll.SA_CTL_FindDevices(
                None, buffer, buffersize)

            # Handle errors
            if result:
                msg_str = 'No MCS2 devices found'
                self.log.error(msg_str)

            #Establishes a connection to a device
            self.dev_name = buffer.value.decode("utf-8")
            dhandle = ctypes.c_uint32()
            connect = self._nanopositionersdll.SA_CTL_Open(
                dhandle, buffer.value, None)
            if connect == 0:
                self.dhandle = dhandle.value
                self.log.info(
                    f'Connected to device {self.dev_name} with handle {self.dhandle}'
                )
            else:
                msg_str = f'Failed to connect to device {self.dev_name}'
                self.log.error(msg_str)

            # Get channel information
            channel_buffer = ctypes.c_int32()
            channel_buffer_size = ctypes.c_size_t(
                ctypes.sizeof(channel_buffer))
            channel_result = self._nanopositionersdll.SA_CTL_GetProperty_i32(
                self.dhandle, 0, self.PKEY_NUM_CH, channel_buffer,
                channel_buffer_size)
            self.num_ch = channel_buffer.value
            if channel_result == 0 and self.num_ch > 0:
                self.log.info(
                    f'Found {self.num_ch} channels on {self.dev_name}')
            else:
                msg_str = f'Failed to find channels on {self.dev_name}'
                self.log.error(msg_str)

        except WindowsError:
            self.log.warn('Did not find MCS2 DLL, entering dummy mode')
            self.dummy = True

    def close(self):
        """ Closes connection to device"""

        connect = self._nanopositionersdll.SA_CTL_Close(self.dhandle)
        if connect:
            msg_str = f'Failed to properly close connection to {self.dev_name}'
            self.log.warn(msg_str)
        else:
            self.log.info(f'Disconnected from {self.dev_name}')

    def set_parameters(self,
                       channel,
                       mode=None,
                       frequency=None,
                       amplitude=None,
                       dc_vel=None):
        """ Sets parameters for motion

        Leave parameter as None in order to leave un-changed

        :param channel: (int) index of channel from 0 to self.num_ch
        :param mode: (str) default is 'step', can use 'dc', 'dc_rel' to set abs or rel DC voltage
        :param freq: (int) frequency in Hz from 1 to 20000
        :param amp: (float) amplitude in volts from 0 to 100
        :param dc_vel: (float) velocity for DC steps in volts/sec
        """

        # Set movement mode
        if mode is not None:
            if mode == 'dc':
                result_mode = self._nanopositionersdll.SA_CTL_SetProperty_i32(
                    self.dhandle, channel, self.PKEY_MOVE_MODE,
                    self.MOVE_MODE_DC_ABS)
                if result_mode:
                    self.log.warn(
                        f'Failed to set mode to DC for positioner {self.dev_name},'
                        f' channel {channel}')
            elif mode == 'dc_rel':
                result_mode = self._nanopositionersdll.SA_CTL_SetProperty_i32(
                    self.dhandle, channel, self.PKEY_MOVE_MODE,
                    self.MOVE_MODE_DC_REL)
                if result_mode:
                    self.log.warn(
                        f'Failed to set mode to DC for positioner {self.dev_name},'
                        f' channel {channel}')
            else:
                result_mode = self._nanopositionersdll.SA_CTL_SetProperty_i32(
                    self.dhandle, channel, self.PKEY_MOVE_MODE,
                    self.MOVE_MODE_STEP)
                if result_mode:
                    self.log.warn(
                        f'Failed to set mode to step for positioner {self.dev_name},'
                        f' channel {channel}')

        # Set frequency
        if frequency is not None:

            # Check for reasonable range
            if 1 <= frequency <= 20000:
                result_freq = self._nanopositionersdll.SA_CTL_SetProperty_i32(
                    self.dhandle, channel, self.PKEY_STEP_FREQ, int(frequency))
                if result_freq:
                    self.log.warn(
                        f'Failed to set step frequency to {frequency} for positioner '
                        f'{self.dev_name}, channel {channel}')

            # Handle out of range request
            else:
                self.log.warn(
                    'Warning, can only set frequency within 1 Hz and 20 kHz')

        # Set amplitude
        if amplitude is not None:

            # Check for reasonable range
            bit_amp = value_to_bitval(amplitude, bits=16, min=0, max=100)
            if 1 <= bit_amp <= 65535:
                result_amp = self._nanopositionersdll.SA_CTL_SetProperty_i32(
                    self.dhandle, channel, self.PKEY_STEP_AMP, bit_amp)
                if result_amp:
                    self.log.warn(
                        f'Failed to set step amplitude to {amplitude} for positioner '
                        f'{self.dev_name}, channel {channel}')
            else:
                self.log.warn(
                    'Warning, can only set amplitude in the range of 0 to 100 V'
                )

        # Set DC velocity
        if dc_vel is not None:

            # Check for reasonable range
            bit_vel = int(dc_vel * (self.SCALE / 100))
            if 1 <= bit_vel <= self.SCALE * 10**6:
                result_vel = self._nanopositionersdll.SA_CTL_SetProperty_i64(
                    self.dhandle, channel, self.PKEY_DC_VEL, bit_vel)
                if result_vel:
                    self.log.warn(
                        f'Failed to set DC velocity to {dc_vel} V/s for positioner '
                        f'{self.dev_name}, channel {channel}')
            else:
                self.log.warn('Warning, can only set velocity in the range of '
                              f'{self.DC_VEL_MIN} to {self.DC_VEL_MAX} V/s')

    def get_voltage(self, channel):
        """ Returns the current DC voltage on a piezo

        :param channel: (int) channel index (from 0)
        """

        voltage_buffer = ctypes.c_int64()
        voltage_buffer_size = ctypes.c_size_t(ctypes.sizeof(voltage_buffer))
        voltage_result = self._nanopositionersdll.SA_CTL_GetProperty_i64(
            self.dhandle, channel, self.PKEY_VOLT, voltage_buffer,
            voltage_buffer_size)

        # Handle error
        if voltage_result:
            self.log.warn(f'Could not retrieve voltage for channel {channel}'
                          f'on device {self.dev_name}')

        return bitval_to_value(voltage_buffer.value, bits=16, min=0, max=100)

    def set_voltage(self, channel, voltage=50):
        """ Sets an absolute voltage to the piezo

        :param channel: (int) channel index (from 0)
        :param voltage: (float) voltage to set from 0 to 100 V (default is 50)
        """

        # Change the move mode to voltage absolute
        self.set_parameters(channel, mode='dc')

        # Move to desired voltage
        bit_voltage = value_to_bitval(voltage, bits=16, min=0, max=100)
        move_result = self._nanopositionersdll.SA_CTL_Move(
            self.dhandle, channel, bit_voltage, 0)

        # Check success
        if move_result:
            self.log.warn(
                f'Failed to set DC voltage to {voltage} V on channel {channel} of {self.dev_name}'
            )

    def n_steps(self, channel, n=1):
        """ Takes n steps

        :param channel: (int) channel index (from 0)
        :param n: (int) number of steps to take, negative is in opposite direction
        """

        # Take the step
        self.set_parameters(channel, mode='step')
        result_step = self._nanopositionersdll.SA_CTL_Move(
            self.dhandle, channel, n, 0)

        # Handle error
        if result_step:
            self.log.warn(
                f'Failed to take {n} steps on device {self.dev_name}, channel {channel}'
            )

    def move(self, channel, backward=False):
        """ Takes the maximum number of steps (quasi continuous)

        :param channel: (int) channel index (from 0)
        :param backward: (bool) whether or not to step in backwards direction (default False)
        """

        # Configure move
        self.set_parameters(channel, mode='step')
        if backward:
            MOVE_STEPS = -100000
        else:
            MOVE_STEPS = 100000

        # Send move command
        result_move = self._nanopositionersdll.SA_CTL_Move(
            self.dhandle, channel, MOVE_STEPS, 0)

        # Handle error
        if result_move:
            self.log.warn(
                f'Failed to take move on device {self.dev_name}, channel {channel}'
            )

    def stop(self, channel):
        """ Terminates any ongoing movement

        :param channel: (int) channel index (from 0)
        """

        result_stop = self._nanopositionersdll.SA_CTL_Stop(
            self.dhandle, channel, 0)
        if result_stop:
            self.log.warn(
                f'Failed to stop movement on device {self.dev_name}, channel {channel}'
            )

    def is_moving(self, channel):
        """ Returns whether or not the positioner is moving

        :param channel: (int) channel index (from 0)

        :return: (bool) true if moving
        """

        # Get the state bit
        current_state_buffer = ctypes.c_int32()
        current_state_buffer_size = ctypes.c_size_t(
            ctypes.sizeof(current_state_buffer))
        state_result = self._nanopositionersdll.SA_CTL_GetProperty_i32(
            self.dhandle, channel, self.PKEY_CURRENT_STATE,
            current_state_buffer, current_state_buffer_size)

        # Handle an error
        if state_result:
            self.log.warn(
                f'Failed to check if positioner {self.dev_name} is moving on'
                f'Channel {channel}')

        # Decode state bit
        if current_state_buffer.value % 2 == 0:
            return False
        else:
            return True

    # Technical methods

    def _configure_functions(self):
        """ Defines arguments and results for c functions """

        # Device connection, disconnection
        self._nanopositionersdll.SA_CTL_GetFullVersionString.restype = ctypes.c_char_p

        self._nanopositionersdll.SA_CTL_FindDevices.argtypes = [
            ctypes.c_char_p,  # options for find procedure
            ctypes.POINTER(
                ctypes.c_char),  # pointer to buffer for writing device locator
            ctypes.POINTER(ctypes.c_ulonglong
                           )  # pointer to variable holding size of buffer
        ]
        self._nanopositionersdll.SA_CTL_FindDevices.restype = ctypes.c_uint32  # result status

        self._nanopositionersdll.SA_CTL_Open.argtypes = [
            ctypes.POINTER(
                ctypes.c_uint32
            ),  # pointer to device handle for use in future calls
            ctypes.c_char_p,  # device locator
            ctypes.c_char_p  # config (unused)
        ]
        self._nanopositionersdll.SA_CTL_Open.restypes = ctypes.c_uint32  # result status

        self._nanopositionersdll.SA_CTL_Close.argtype = ctypes.c_uint32  # device handle
        self._nanopositionersdll.SA_CTL_Close.restype = ctypes.c_uint32  # result status

        # Getting device properties
        self._nanopositionersdll.SA_CTL_GetProperty_i32.argtypes = [
            ctypes.c_uint32,  # device handle
            ctypes.c_int8,  # index of addressed device, module, or channel
            ctypes.c_uint32,  # property key
            ctypes.POINTER(
                ctypes.c_int32),  # pointer to buffer where result is written
            ctypes.POINTER(
                ctypes.c_ulonglong
            )  # pointer to size of value buffer (number of elements)
        ]
        self._nanopositionersdll.SA_CTL_GetProperty_i32.restype = ctypes.c_uint32  # result status

        self._nanopositionersdll.SA_CTL_GetProperty_i64.argtypes = [
            ctypes.c_uint32,  # device handle
            ctypes.c_int8,  # index of addressed device, module, or channel
            ctypes.c_uint32,  # property key
            ctypes.POINTER(
                ctypes.c_int64),  # pointer to buffer where result is written
            ctypes.POINTER(ctypes.c_ulonglong
                           )  # pointer to size of value buffer (# of elements)
        ]
        self._nanopositionersdll.SA_CTL_GetProperty_i64.restype = ctypes.c_uint32  # result status

        # Settting device properties
        self._nanopositionersdll.SA_CTL_SetProperty_i32.argtypes = [
            ctypes.c_uint32,  # device handle
            ctypes.c_int8,  # index of addressed device, module, or channel
            ctypes.c_uint32,  # property key
            ctypes.c_int32,  # value to write
        ]
        self._nanopositionersdll.SA_CTL_SetProperty_i32.restype = ctypes.c_uint32  # result status

        self._nanopositionersdll.SA_CTL_SetProperty_i64.argtypes = [
            ctypes.c_uint32,  # device handle
            ctypes.c_int8,  # index of addressed device, module, or channel
            ctypes.c_uint32,  # property key
            ctypes.c_int64  # value to write
        ]
        self._nanopositionersdll.SA_CTL_SetProperty_i64.restype = ctypes.c_uint32  # result status

        # Movement
        self._nanopositionersdll.SA_CTL_Move.argtypes = [
            ctypes.c_uint32,  # device handle
            ctypes.c_int8,  # index of addressed device, module, or channel
            ctypes.c_int64,  # move value
            ctypes.c_uint32  # transmit handle
        ]
        self._nanopositionersdll.SA_CTL_Move.restype = ctypes.c_uint32  # result status

        self._nanopositionersdll.SA_CTL_Stop.argtypes = [
            ctypes.c_uint32,  # device handle
            ctypes.c_int8,  # index of addressed device, module, or channel
            ctypes.c_uint32  # transmit handle
        ]
        self._nanopositionersdll.SA_CTL_Stop.restype = ctypes.c_uint32  # result status
Beispiel #8
0
class Driver():
    def __init__(self, channels, logger=None):
        """ Initializes connection to all TP Kasa smart plugs in the network.

        :channels: list of channels accessaable via this smart plug interface
        """

        # Instantiate log.
        self.log = LogHandler(logger=logger)

        self.channels = channels

        # Discover smart plugs.
        self.found_devices = asyncio.run(Discover.discover())

        # Store aliases of found devices.
        self.found_device_aliases = [
            dev.alias for dev in self.found_devices.values()
        ]

        self.log.info(
            f"Discovered {len(self.found_device_aliases)} smart plugs.")

    def retrieve_device(self, channel_id):
        """ Returns kasa.SmartPlug object corresponding to a specific plug.

        The plugs are identified with channel_ids, which usually correspond to human readable names of the plug location,
            e.g. "Lights Kitchen". The config file then matches the channel_ids to the plug aliases,
            as they have been defined using the Kasa app.
        :channel_id: (str) Human readable device ID.
        """
        _, alias = self.get_device_info(channel_id)

        if not alias in self.found_device_aliases:
            self.log.error(
                f"Smart Plug at location '{channel_id}' with plug alias '{alias}' not found. Connected devices are: {self.found_device_aliases}"
            )
        else:
            self.log.info(
                f"Smart Plug at location '{channel_id}' with plug alias '{alias}' discovered."
            )

        device = [
            dev for dev in self.found_devices.values() if dev.alias == alias
        ][0]

        # Run update.
        asyncio.run(device.update())

        return device

    def turn_on(self, channel_id):
        """Turn plug on.

        :channel_id: (str) Human readable device ID.
        """

        device = self.retrieve_device(channel_id)
        asyncio.run(device.turn_on())
        self.log.info(f"Smart Plug at location '{channel_id}' turned on.")

    def turn_off(self, channel_id):
        """Turn plug off.

        :channel_id: (str) Human readable device ID.
        """

        device = self.retrieve_device(channel_id)
        asyncio.run(device.turn_off())
        self.log.info(f"Smart Plug at location  '{channel_id}' turned off.")

    def is_on(self, channel_id):
        """ Returns True is plug is on.

        :channel_id: (str) Human readable channel ID.
        """
        device = self.retrieve_device(channel_id)
        return device.is_on

    def get_device_info(self, channel_id):
        """ Read config dict and find alias matching to channel_id.

        :channel_id: (str) Human readable device ID.
        """

        alias, current_plug_type = None, None

        for location_dict in self.channels:
            if location_dict['channel_id'] == channel_id:
                alias = location_dict['alias']
                break

        if alias is not None:
            return current_plug_type, alias
        else:
            self.log.error(
                f"Could not find plug location {channel_id} in smart_plug_config.json."
            )
Beispiel #9
0
class Controller:
    """ Class for controlling Toptica scan and laser properties """
    def __init__(self,
                 dlc: toptica_dl_pro.Client,
                 gui='toptica_control',
                 logger=None,
                 port=None):
        """ Initializes toptica specific parameters

        :param dlc: DLC client for the Toptica laser
        :param gui: .ui file to use
        :param logger: LogClient for logging purposes
        :param port: port number of script server
        """

        self.log = LogHandler(logger)

        # Setup GUI
        self.gui = Window(gui_template=gui, host=get_ip(), port=port)
        self.widgets = get_gui_widgets(gui=self.gui,
                                       on_off=2,
                                       temperature=2,
                                       temperature_actual=2,
                                       current=2,
                                       current_actual=2,
                                       offset=2,
                                       amplitude=2,
                                       frequency=2,
                                       scan=2,
                                       update_temp=2,
                                       update_current=2,
                                       update_params=1)

        self.dlc = dlc
        self.offset = 65
        self.amplitude = 100
        self.scan = [False, False]
        self.emission = [False, False]

        # Setup stylesheet.
        self.gui.apply_stylesheet()

        self._setup_GUI()

    def run(self, check_vals=False):
        """ Runs an iteration of checks for updates and implements

        :param check_vals: (bool) whether or not to check the values of current and temp
        """

        # Update actual current and temperature
        # self.gui.activate_scalar('temperature_actual')
        # self.gui.set_scalar(self.dlc.temp_act(), 'temperature_actual')
        # self.gui.deactivate_scalar('temperature_actual')

        # self.gui.activate_scalar('current_actual')
        # self.gui.set_scalar(self.dlc.current_act(), 'current_actual')
        # self.gui.deactivate_scalar('current_actual')

        # Check for on/off updates
        for i in range(2):
            if self.widgets['on_off'][i].isChecked() != self.emission[i]:

                # If laser was already on, turn off
                if self.emission[i]:
                    self.dlc.turn_off(i + 1)
                    self.emission[i] = False
                    self.log.info(f'Toptica DL {i+1} turned off')

                # Otherwise turn on
                else:
                    self.dlc.turn_on(i + 1)
                    self.emission[i] = True
                    self.log.info(f'Toptica DL {i+1} turned on')

        # Now handle a scan event
        for i in range(2):
            if self.widgets['scan'][i].isChecked() != self.scan[i]:

                # If we were previously scanning, terminate the scan
                if self.scan[i]:
                    self.dlc.stop_scan(i + 1)
                    self.scan[i] = False
                    self.log.info(f'Toptica DL {i+1} scan stopped')

                # Otherwise, configure and start the scan
                else:
                    offset = self.widgets['offset'][i].value()
                    amplitude = self.widgets['amplitude'][i].value()
                    frequency = self.widgets['frequency'][i].value()
                    self.dlc.configure_scan(offset=offset,
                                            amplitude=amplitude,
                                            frequency=frequency,
                                            laser_num=i + 1)
                    self.dlc.start_scan(i + 1)
                    self.scan[i] = True
                    self.log.info(f'Toptica DL Scan {i+1} initiated '
                                  f'with offset: {offset}, '
                                  f'amplitude: {amplitude}, '
                                  f'frequency: {frequency}')

        # Handle value checking
        if self.widgets['update_params'].isChecked():

            for i in range(2):
                self.widgets['temperature_actual'][i].setDisabled(False)
                self.widgets['current_actual'][i].setDisabled(False)

            if check_vals:
                try:
                    temp_1 = self.dlc.temp_act(1)
                    if temp_1 < 50:
                        self.widgets['temperature_actual'][0].setValue(temp_1)
                    time.sleep(0.1)
                    self.widgets['current_actual'][0].setValue(
                        self.dlc.current_act(1))
                    temp_2 = self.dlc.temp_act(2)
                    if temp_2 < 50:
                        self.widgets['temperature_actual'][1].setValue(temp_2)
                    time.sleep(0.1)
                    self.widgets['current_actual'][1].setValue(
                        self.dlc.current_act(2))
                except ValueError:
                    pass
        else:
            for i in range(2):
                self.widgets['temperature_actual'][i].setDisabled(True)
                self.widgets['current_actual'][i].setDisabled(True)

        self.gui.force_update()

    def _setup_GUI(self):
        """ Sets values to current parameters """

        # Check if laser is on and update

        for i in range(2):
            self.emission[i] = self.dlc.is_laser_on(i + 1)
            self.widgets['on_off'][i].setChecked(self.emission[i])
            time.sleep(0.1)

        # Get temperature setpoint and actual temperature
        temp_sp_1 = 100
        while temp_sp_1 > 50:
            temp_sp_1 = self.dlc.temp_sp(1)
            temp_sp_2 = self.dlc.temp_sp(2)
            time.sleep(0.1)

        self.widgets['temperature'][0].setValue(temp_sp_1)
        self.widgets['temperature'][1].setValue(temp_sp_2)

        temp_act_1 = 100
        while temp_act_1 > 50:
            temp_act_1 = self.dlc.temp_act(1)
            temp_act_2 = self.dlc.temp_act(2)
            time.sleep(0.1)
        self.widgets['temperature_actual'][0].setValue(temp_act_1)
        self.widgets['temperature_actual'][1].setValue(temp_act_2)

        # Get current setpoint and actual current
        self.widgets['current'][0].setValue(self.dlc.current_sp(1))
        time.sleep(0.1)
        self.widgets['current'][1].setValue(self.dlc.current_sp(2))
        time.sleep(0.1)
        self.widgets['current_actual'][0].setValue(self.dlc.current_act(1))
        time.sleep(0.1)
        self.widgets['current_actual'][1].setValue(self.dlc.current_act(2))
        time.sleep(0.1)

        # Assign button pressing
        self.widgets['update_temp'][0].clicked.connect(
            lambda: self._set_temperature(1))
        self.widgets['update_temp'][1].clicked.connect(
            lambda: self._set_temperature(2))
        self.widgets['update_current'][0].clicked.connect(
            lambda: self._set_current(1))
        self.widgets['update_current'][1].clicked.connect(
            lambda: self._set_current(2))

    def _set_temperature(self, laser_num):
        """ Sets the temperature to the setpoint value in the GUI """

        temperature = self.widgets['temperature'][laser_num - 1].value()
        self.dlc.set_temp(temperature, laser_num)
        self.log.info(
            f'Set Toptica {laser_num} temperature setpoint to {temperature}')

    def _set_current(self, laser_num):
        """ Sets the current to the setpoint value in the GUI """

        current = self.widgets['current'][laser_num - 1].value()
        self.dlc.set_current(current, laser_num)
        self.log.info(f'Set Toptica {laser_num} current setpoint to {current}')
Beispiel #10
0
class Driver():
    POWER_RANGE = [-120, 30]  # acceptable power range in dBm
    POWER_PRECISION = 2  # number of digits of precision for power
    FREQ_RANGE = [1e7, 2e10]  # acceptable frequency range in Hz

    def __init__(self, gpib_address, logger):
        """ Instantiate driver class, connects to device

        :param gpib_address: GPIB-address of the device,
            can be found with pyvisa.ResourceManager.list_resources()
        :param logger: instance of LogClient
        """

        self.log = LogHandler(logger=logger)
        self.rm = ResourceManager()

        try:
            self.device = self.rm.open_resource(gpib_address)
            self.device.read_termination = '\n'
            self.device_id = self.device.query('*IDN?')
            self.log.info(f'Successfully connected to {self.device_id}')
        except VisaIOError:
            self.log.error(f'Connection to {gpib_address} failed')
            raise

    def set_power(self, power):
        """ Sets power of MW source

        :param power: (float) output power to set in dBm
        """

        power = round(power, self.POWER_PRECISION)

        # Check for valid range
        if power < self.POWER_RANGE[0] or power > self.POWER_RANGE[1]:
            self.log.warn(
                f'Warning, power outside acceptable range {self.POWER_RANGE}. '
                f'Output power was not updated.')
        # Set power
        else:
            self.device.write(f'POW {power}')
            self.log.info(f'Set MW power to {power}')

    def set_freq(self, freq):
        """ Sets power of MW source

        :param freq: (float) output frequency in Hz
        """

        freq = int(round(freq))

        # Check for valid range
        if freq < self.FREQ_RANGE[0] or freq > self.FREQ_RANGE[1]:
            self.log.warn(
                f'Warning, frequency outside acceptable range {self.FREQ_RANGE}. '
                f'Output frequency was not updated.')
        # Set freq
        else:
            self.device.write(f'FREQ {freq}')
            self.log.info(f'Set MW freq to {freq}')

    def output_on(self):
        """ Turn output on """

        self.device.write('OUTP ON')
        self.log.info('MW output turned on')

    def output_off(self):
        """ Turn output off """

        self.device.write('OUTP OFF')
        self.log.info('MW output turned off')
Beispiel #11
0
class Driver:

    BOARDS = 8

    def __init__(self, address=None, logger=None):
        """Instantiate driver class.

        :address: Address of the device, e.g. 'ASRL3::INSTR'
            Can be read out by using
                rm = pyvisa.ResourceManager()
                rm.list_resources()
        :logger: An instance of a LogClient.
        """

        # Instantiate log
        self.log = LogHandler(logger=logger)
        self.addr = address

        self.rm = ResourceManager()

        try:
            self.device = self.rm.open_resource(self.addr)
            self.log.info(f"Successfully connected to {self.device}.")

            # Configure device grammar
            self.device.write_termination = ';'
        except VisaIOError:
            self.log.error(f"Connection to {self.addr} failed.")
            raise

    def measure_voltage(self, board, channel):
        """ Measures the current voltage on a particular channel of a particular board

        :param board: (int) integer between 0 and 7 (assuming 8 boards)
        :param channel: (int) integer between 0 and 3

        :return: (float) voltage in volts (into open-loop)
        """

        # Only proceed if we can correctly set the current board and channel
        if self._set_board(board) + self._set_channel(channel):
            self.log.warn(
                f'Did not measure the voltage for board {board} channel {channel}'
            )
            return float(-777)

        return bitval_to_value(int(self.device.query('v').split()[-1]),
                               bits=12,
                               min=0,
                               max=10)

    def set_high_voltage(self, board, channel, voltage):
        """ Sets a channel's high voltage

        :param board: (int) integer between 0 and 7 (assuming 8 boards)
        :param channel: (int) integer between 0 and 3
        :param voltage: (float) voltage in V between 0 and 10 (into open-loop)

        :return: (int) 0 if successful
        """

        # Only proceed if we can correctly set the current board and channel
        if self._set_board(board) + self._set_channel(channel):
            self.log.warn(
                f'Did not measure the voltage for board {board} channel {channel}'
            )
            return float(-777)

        return self._set_high_voltage(voltage)

    def set_low_voltage(self, board, channel, voltage):
        """ Sets a channel's low voltage

        :param board: (int) integer between 0 and 7 (assuming 8 boards)
        :param channel: (int) integer between 0 and 3
        :param voltage: (float) voltage in V between 0 and 10 (into open-loop)

        :return: (int) 0 if successful
        """

        # Only proceed if we can correctly set the current board and channel
        if self._set_board(board) + self._set_channel(channel):
            self.log.warn(
                f'Did not measure the voltage for board {board} channel {channel}'
            )
            return float(-777)

        return self._set_low_voltage(voltage)

    def get_high_voltage(self, board, channel):
        """ Gets a channel's high voltage

        :param board: (int) integer between 0 and 7 (assuming 8 boards)
        :param channel: (int) integer between 0 and 3

        :return: (float) voltage in V from 0 to 10
        """

        # Only proceed if we can correctly set the current board and channel
        if self._set_board(board) + self._set_channel(channel):
            self.log.warn(
                f'Did not measure the voltage for board {board} channel {channel}'
            )
            return float(-777)

        return bitval_to_value(int(self.device.query('h').split()[-1]),
                               bits=16,
                               min=0,
                               max=10)

    def get_low_voltage(self, board, channel):
        """ Gets a channel's low voltage

        :param board: (int) integer between 0 and 7 (assuming 8 boards)
        :param channel: (int) integer between 0 and 3

        :return: (float) voltage in V from 0 to 10
        """

        # Only proceed if we can correctly set the current board and channel
        if self._set_board(board) + self._set_channel(channel):
            self.log.warn(
                f'Did not measure the voltage for board {board} channel {channel}'
            )
            return float(-777)

        return bitval_to_value(int(self.device.query('l').split()[-1]),
                               bits=16,
                               min=0,
                               max=10)

    def save(self):
        """ Saves current state of low/high for all channels to non-volatile memory

        :return: (int) 0 if successful
        """

        # This fails randomly due to nondeterministic response, which we need to handle
        read = 0
        while read < 10:
            self.device.write('S')
            try:
                self.device.read()
                read = 11
            except VisaIOError:
                read += 1
            except UnicodeDecodeError:
                read += 1
        if read > 10:
            self.log.info('Saved current DIO breakout settings successfully')
            return 0
        else:
            self.log.warn('Failed to save DIO breakout settings.'
                          'Connection may be corrupted.')
            return 1

    def override(self, board, channel, state=True):
        """ Overrides HDAWG output

        :param board: (int) integer between 0 and 7 (assuming 8 boards)
        :param channel: (int) integer between 0 and 3
        :param state: (bool) whether or not to force hi or lo

        :return: (int) 0 if successful
        """

        if self._set_board(board) + self._set_channel(channel):
            self.log.warn(f'Did not override board {board} channel {channel}')
            return float(-777)

        self.device.write(f'F {1 if state else 0}')
        if int(self.device.query('b').rstrip()[-1]) != board:
            self.log.warn(
                f'Error in overriding board {board} channel {channel}')
            return float(-1)

        self.log.info(f'Board {board} channel {channel} in override mode')
        return 0

    def disable_override(self, board, channel):
        """ Disables the override

        :param board: (int) integer between 0 and 7 (assuming 8 boards)
        :param channel: (int) integer between 0 and 3

        :return: (int) 0 if successful
        """

        if self._set_board(board) + self._set_channel(channel):
            self.log.warn(
                f'Did not disable override for board {board} channel {channel}'
            )
            return float(-777)

        self.device.write('F -1')
        if int(self.device.query('b').rstrip()[-1]) != board:
            self.log.warn(
                f'Error in disabling override for board {board} channel {channel}'
            )
            return float(-1)

        self.log.info(
            f'Board {board} channel {channel} override has been disabled')
        return 0

    def close(self):
        """ Closes the connection to the device """

        self.device.close()
        self.log.info(f'Closed connection to device at {self.addr}')

    # Technical methods (not to be exposed)

    def _set_board(self, board):
        """ Sets the current board (to update)

        :param board: (int) integer between 0 and 7 (assuming 8 boards)

        :return: (int) 0 if successful
        """

        board = int(board)
        # Check within bounds
        if board < 0 or board > self.BOARDS - 1:
            self.log.warn(
                f'Board to set must be an integer between 0 and {self.BOARDS-1}'
            )
            return 1

        self.device.write(f'B {board}')

        # Check successful write (and clear the buffer)
        if int(self.device.query('b').rstrip()[-1]) != board:
            self.log.warn(f'Failed to set current board to {board}')
            return 2

        return 0

    def _set_channel(self, channel):
        """ Sets the current channel (to update)

        :param channel: (int) integer between 0 and 3

        :return: (int) 0 if successful
        """

        channel = int(channel)
        # Check within bounds
        if channel < 0 or channel > 3:
            self.log.warn(f'Channel to set must be an integer between 0 and 3')
            return 1

        self.device.write(f'C {channel}')

        # Check successful write (and clear the buffer)
        if int(self.device.query('c').rstrip()[-1]) != channel:
            self.log.warn(f'Failed to set current channel to {channel}')
            return 2

        return 0

    def _set_high_voltage(self, voltage):
        """ Sets the current channel's high voltage

        :param voltage: (float) voltage in V between 0 and 10 (into open-loop)

        :return: (int) 0 if successful
        """

        voltage = float(voltage)
        # Check within bounds
        if voltage < 0 or voltage > 10:
            self.log.warn(f'Can only set voltage between 0 and 10 V')
            return 1

        bitval = value_to_bitval(voltage, bits=16, min=0, max=10)
        self.device.write(f'H {bitval}')

        # Check successful write (and clear the buffer)
        if int(self.device.query('h').split()[-1]) != bitval:
            self.log.warn(f'Failed to set high voltage to {voltage} V')
            return 2

        return 0

    def _set_low_voltage(self, voltage):
        """ Sets the current channel's low voltage

        :param voltage: (float) voltage in V between 0 and 10 (into open-loop)

        :return: (int) 0 if successful
        """

        voltage = float(voltage)
        # Check within bounds
        if voltage < 0 or voltage > 10:
            self.log.warn(f'Can only set voltage between 0 and 10 V')
            return 1

        bitval = value_to_bitval(voltage, bits=16, min=0, max=10)
        self.device.write(f'L {bitval}')

        # Check successful write (and clear the buffer)
        if int(self.device.query('l').split()[-1]) != bitval:
            self.log.warn(f'Failed to set low voltage to {voltage} V')
            return 2

        return 0
Beispiel #12
0
class Driver():

    def __init__(self, device_id, logger, dummy=False, api_level=6, reset_dio=False, disable_everything=False, **kwargs):
        """ Instantiate AWG

        :logger: instance of LogClient class
        :device_id: Device id of connceted ZI HDAWG, for example 'dev8060'
        :api_level: API level of zhins API
        """

        # Instantiate log
        self.log = LogHandler(logger=logger)

        # Store dummy flag
        self.dummy = dummy

        # Setup HDAWG
        self._setup_hdawg(device_id, logger, api_level, reset_dio, disable_everything)

    @dummy_wrap
    def reset_DIO_outputs(self):
        """Sets all DIO outputs to low"""
        self.seti('dios/0/output', 0)
        self.log.info("Set all DIO outputs to low.")

    @dummy_wrap
    def disable_everything(self):
        """ Create a base configuration.
        Disable all available outputs, awgs, demods, scopes, etc.
        """
        zhinst.utils.disable_everything(self.daq, self.device_id)
        self.log.info("Disabled everything.")

    @log_standard_output
    @dummy_wrap
    def log_stdout(self, function):
        """ Execute function and log print output to self.log

        This statement is needed for an inline call where any zhinst command is
        executed and the standard output should be logged
        :function: The function to be executed.
         """
        return function()

    def _convert_to_list(self, input_argument):
        """Checks if input is list and if not, converts it to list."""
        if type(input_argument) is not list:
            input_argument = [input_argument]
        return input_argument

    @dummy_wrap
    def _setup_hdawg(self, device_id, logger, api_level, reset_dio, disable_everything):
        ''' Sets up HDAWG '''

        err_msg = "This example can only be run on an HDAWG."

          # Connect to device and log print output, not the lambda expression.
        (daq, device, props) = self.log_stdout(
            lambda: zhinst.utils.create_api_session(
                device_id,
                api_level,
                required_devtype='HDAWG',
                required_err_msg=err_msg
            )
        )

        self.log_stdout(lambda: zhinst.utils.api_server_version_check(daq))

        self.daq = daq
        self.device_id = device

        if disable_everything:
            # Create a base configuration
            self.disable_everything()

        if reset_dio:
            self.reset_DIO_outputs()

        # read out number of channels from property dictionary
        self.num_outputs = int(
            re.compile('HDAWG(4|8{1})').match(props['devicetype']).group(1)
        )

    @log_standard_output
    @dummy_wrap
    def seti(self, node, new_int):
        """
        Warapper for daq.setInt commands. For instance, instead of
        daq.setInt('/dev8040/sigouts/0/on', 1), write

        hdawg.seti('sigouts/0/on, 1)

        :node: Node which will be appended to '/device_id/'
        :new_int: New value for integer
        """

        self.daq.setInt(f'/{self.device_id}/{node}', new_int)

    @log_standard_output
    @dummy_wrap
    def setd(self, node, new_double):
        """
        Warapper for daq.setDouble commands. For instance, instead of
        daq.setDouble('/dev8040/sigouts/0/range', 0.8), write

        hdawg.setd('sigouts/0/range')

        :node: Node which will be appended to '/device_id/'
        :new_double: New value for double.
        """

        self.daq.setDouble(f'/{self.device_id}/{node}', new_double)

    @log_standard_output
    @dummy_wrap
    def getd(self, node):
        """
        Warapper for daq.setDouble commands. For instance, instead of
        daq.getDouble('/dev8040/sigouts/0/range'), write

        hdawg.getd('sigouts/0/range')

        :node: Node which will be appended to '/device_id/'
        """

        return self.daq.getDouble(f'/{self.device_id}/{node}')

    @log_standard_output
    @dummy_wrap
    def setv(self, node, vector):
        """
        Warapper for daq.setVector commands. For instance, instead of
        daq.setVector('/dev8060/awgs/0/waveform/waves/1', vector), write

        hdawg.setd('sigouts/awgs/0/waveform/waves/1', vector)

        :node: Node which will be appended to '/device_id/'
        :new_double: New value for double.
        """

        self.daq.setVector(f'/{self.device_id}/{node}', vector)

    @log_standard_output
    @dummy_wrap
    def geti(self, node):
        """
        Wrapper for daq.getInt commands. For instance, instead of
        daq.getInt('/dev8040/sigouts/0/busy'), write

        hdawg.geti('sigouts/0/busy')

        :node: Node which will be appended to '/device_id/'
        """

        return self.daq.getInt(f'/{self.device_id}/{node}')

    @log_standard_output
    @dummy_wrap
    def gets(self, path):
        """
        Wrapper for daq.getString commands. 
        Get a string value from the specified node.
|       :path: Path string of the node.
        """
        return self.daq.getString(path)

    @dummy_wrap
    def set_channel_grouping(self, index):
        """ Specifies channel grouping.

        :index: Integer indicating channel grouping:
            0 : 4x2 with HDAWG8; 2x2 with HDAWG4.
            1 : 2x4 with HDAWG8; 1x4 with HDAWG4.
            2 : 1x8 with HDAWG8.
        """
        self.seti('system/awg/channelgrouping', index)
        time.sleep(2)

    # Functions related to wave outputs:

    @dummy_wrap
    def _toggle_output(self, output_indices, target_index):
        """
        Local function enabeling/disabeling wave output.
        """

        # If single integer is given, convert to list.
        output_indices = self._convert_to_list(output_indices)

        for output_index in output_indices:
            if output_index in range(self.num_outputs):
                self.seti(f'sigouts/{output_index}/on', target_index)
                if target_index == 1:
                    self.log.info(f"Enabled wave output {output_index}.")
                elif target_index == 0:
                    self.log.info(f"Disable wave output {output_index}.")
            else:
                self.log.error(
                    f"This device has only {self.num_outputs} channels, \
                        channel index {output_index} is invalid."
                )

    @dummy_wrap
    def enable_output(self, output_indices):
        """
        Enables wave output.

        Channel designation uses channel index (0 to 7),
        not channel number (1 to 8).

        :output_index: List or int containing
            integers indicating wave output 0 to 7
        """

        self._toggle_output(output_indices, 1)

    @dummy_wrap
    def disable_output(self, output_indices):
        """
        Disables wave output.

        :output_index: List or int containing
            integers indicating wave output 0 to 7
        """
        self._toggle_output(output_indices, 0)

    @dummy_wrap
    def set_output_range(self, output_index, output_range):
        """
        Set the output range.

        :output_index: List or int containing integers
            indicating wave output 0 to 7
        :output_range: Double indicating the range of wave output, in Volt.
            All waveforms (ranging from 0 to 1 in value) will be multiplied
            with this value. Possible ranges are:
            0.2, 0.4, 0.6, 0.8, 1, 2, 3, 4, 5 (V)
        """

        allowed_ranges = [0.2, 0.4, 0.6, 0.8, 1, 2, 3, 4, 5]

        if output_index in range(self.num_outputs):
            if output_range in allowed_ranges:

                # Send change range command.
                self.setd(f'sigouts/{output_index}/range', output_range)

                # Wait for HDAWG to be ready, try 100 times before timeout.
                max_tries = 100
                num_tries = 0

                while self.geti(f'sigouts/{output_index}/busy') and num_tries < max_tries:
                    time.sleep(0.2)
                    num_tries += 1

                if num_tries is max_tries:
                    self.log.error(
                        f"Range change timeout after {max_tries} tries."
                    )
                else:
                    self.log.info(
                        f"Changed range of wave output {output_index} to {output_range} V."
                    )
            else:
                self.log.error(
                    f"Range {output_range} is not valid, allowed values for range are {allowed_ranges}"
                )
        else:
            self.log.error(
                f"This device has only {self.num_outputs} channels, channel index {output_index} is invalid."
            )

    def set_direct_user_register(self, awg_num, reg_index, value):
        """ Sets a user register to a desired value

        :param awg_num: (int) index of awg module
        :param reg_index: (int) index of user register (from 0-15)
        :param value: (int) value to set user register to
        """

        self.setd(f'awgs/{awg_num}/userregs/{reg_index}', int(value))

    def get_direct_user_register(self, awg_num, reg_index):
        """ Gets a user register to a desired value

        :param awg_num: (int) index of awg module
        :param reg_index: (int) index of user register (from 0-15)
        """

        return int(self.getd(f'awgs/{awg_num}/userregs/{reg_index}'))
Beispiel #13
0
class DataTaker:
    def __init__(self,
                 logger=None,
                 client_tuples=None,
                 config=None,
                 config_name=None):

        self.log = LogHandler(logger)
        self.dataset = None

        # Instantiate GUI window
        self.gui = Window(gui_template='data_taker', host=get_ip())

        # Configure list of experiments
        self.gui.config.setText(config_name)
        self.config = config
        self.exp_path = self.config['exp_path']
        if self.exp_path is None:
            self.exp_path = os.getcwd()
        sys.path.insert(1, self.exp_path)
        self.update_experiment_list()

        # Configure list of clients
        self.clients = {}

        # Retrieve Clients
        for client_entry in self.config['servers']:
            client_type = client_entry['type']
            client_config = client_entry['config']
            client = find_client(clients=client_tuples,
                                 settings=client_config,
                                 client_type=client_type,
                                 client_config=client_config,
                                 logger=self.log)
            self.clients[f"{client_type}_{client_config}"] = client

        for client_name, client_obj in self.clients.items():
            client_item = QtWidgets.QListWidgetItem(client_name)
            client_item.setToolTip(str(client_obj))
            self.gui.clients.addItem(client_item)

        # Configure dataset menu
        for name, obj in inspect.getmembers(datasets):
            if inspect.isclass(obj) and issubclass(obj, datasets.Dataset):
                self.gui.dataset.addItem(name)

        # Configure button clicks
        self.gui.configure.clicked.connect(self.configure)
        self.gui.run.clicked.connect(self.run)
        self.gui.save.clicked.connect(self.save)
        self.gui.load_config.clicked.connect(self.reload_config)
        self.gui.showMaximized()
        self.gui.apply_stylesheet()

    def update_experiment_list(self):
        """ Updates list of experiments """

        self.gui.exp.clear()
        for filename in os.listdir(self.exp_path):
            if filename.endswith('.py'):
                self.gui.exp.addItem(filename[:-3])
        self.gui.exp.itemClicked.connect(self.display_experiment)

    def display_experiment(self, item):
        """ Displays the currently clicked experiment in the text browser

        :param item: (QlistWidgetItem) with label of name of experiment to display
        """

        with open(os.path.join(self.exp_path, f'{item.text()}.py'),
                  'r') as exp_file:
            exp_content = exp_file.read()

        self.gui.exp_preview.setText(exp_content)
        self.gui.exp_preview.setStyleSheet('font: 10pt "Consolas"; '
                                           'color: rgb(255, 255, 255); '
                                           'background-color: rgb(0, 0, 0);')
        self.log.update_metadata(experiment_file=exp_content)

    def configure(self):
        """ Configures the currently selected experiment + dataset """

        # If the experiment is running, do nothing
        try:
            if self.experiment_thread.isRunning():
                self.log.warn('Did not configure experiment, since it '
                              'is still in progress')
                return
        except:
            pass

        # Load the config
        self.reload_config()

        # Set all experiments to normal state and highlight configured expt
        for item_no in range(self.gui.exp.count()):
            self.gui.exp.item(item_no).setBackground(
                QtGui.QBrush(QtGui.QColor('black')))
        self.gui.exp.currentItem().setBackground(
            QtGui.QBrush(QtGui.QColor('darkRed')))
        exp_name = self.gui.exp.currentItem().text()
        self.module = importlib.import_module(exp_name)
        self.module = importlib.reload(self.module)

        # Clear graph area and set up new or cleaned up dataset
        for index in reversed(range(self.gui.graph_layout.count())):
            try:
                self.gui.graph_layout.itemAt(index).widget().deleteLater()
            except AttributeError:
                try:
                    self.gui.graph_layout.itemAt(index).layout().deleteLater()
                except AttributeError:
                    pass
        self.gui.windows = {}
        # If we're not setting up a new measurement type, just clear the data
        self.dataset = getattr(datasets, self.gui.dataset.currentText())(
            gui=self.gui, log=self.log, config=self.config)

        # Run any pre-experiment configuration
        try:
            self.module.configure(dataset=self.dataset, **self.clients)
        except AttributeError:
            pass
        self.experiment = self.module.experiment

        self.log.info(f'Experiment {exp_name} configured')
        self.gui.exp_preview.setStyleSheet(
            'font: 10pt "Consolas"; '
            'color: rgb(255, 255, 255); '
            'background-color: rgb(50, 50, 50);')

    def run(self):
        """ Runs/stops the experiment """

        # Run experiment
        if self.gui.run.text() == 'Run':
            self.gui.run.setStyleSheet('background-color: red')
            self.gui.run.setText('Stop')
            self.log.info('Experiment started')

            # Run update thread
            self.update_thread = UpdateThread(
                autosave=self.gui.autosave.isChecked(),
                save_time=self.gui.autosave_interval.value())
            self.update_thread.data_updated.connect(self.dataset.update)
            self.update_thread.save_flag.connect(self.save)
            self.gui.autosave.toggled.connect(
                self.update_thread.update_autosave)
            self.gui.autosave_interval.valueChanged.connect(
                self.update_thread.update_autosave_interval)

            self.experiment_thread = ExperimentThread(self.experiment,
                                                      dataset=self.dataset,
                                                      gui=self.gui,
                                                      **self.clients)

            self.experiment_thread.status_flag.connect(
                self.dataset.interpret_status)
            self.experiment_thread.finished.connect(self.stop)
            self.log.update_metadata(
                exp_start_time=datetime.now().strftime('%d/%m/%Y %H:%M:%S:%f'))

        # Stop experiment
        else:
            self.experiment_thread.running = False

    def stop(self):
        """ Stops the experiment"""

        self.gui.run.setStyleSheet('background-color: green')
        self.gui.run.setText('Run')
        self.log.info('Experiment stopped')
        self.update_thread.running = False

        self.log.update_metadata(
            exp_stop_time=datetime.now().strftime('%d/%m/%Y %H:%M:%S:%f'))

        # Autosave if relevant
        if self.gui.autosave.isChecked():
            self.save()

    def save(self):
        """ Saves data """

        self.log.update_metadata(notes=self.gui.notes.toPlainText())
        filename = self.gui.save_name.text()
        directory = self.config['save_path']
        self.dataset.save(filename=filename,
                          directory=directory,
                          date_dir=True)
        save_metadata(self.log, filename, directory, True)
        self.log.info('Data saved')

    def reload_config(self):
        """ Loads a new config file """

        self.config = load_script_config(script='data_taker',
                                         config=self.gui.config.text(),
                                         logger=self.log)
Beispiel #14
0
class Driver(MWSrcInterface):
    """Adapted from Qudi <https://github.com/Ulm-IQO/qudi/>
    """
    def __init__(self, addr_str, logger=None):

        self.log = LogHandler(logger=logger)

        # Connect to the device
        self.rm = visa.ResourceManager()
        try:
            self._dev = self.rm.open_resource(addr_str)
        except Exception as exc_obj:
            self.log.exception(
                msg_str='Could not connect to the address >>{}<<. \n'.format(
                    addr_str))
            raise exc_obj

        # Log confirmation info message
        id_str = self._dev.query('*IDN?').replace(',', ' ')
        id_str = id_str.strip('\n')
        self.log.info(msg_str='{} initialised and connected.'.format(id_str))

        # Reset device
        self.reset()

        # Error check
        self._er_chk()

    def reset(self):
        # Reset
        self._cmd_wait('*RST')
        # Clear status register
        self._cmd_wait('*CLS')

        return 0

    def activate_interface(self):

        # Store hardware settings which are not controlled by logic,
        # to restore them after reset()
        # [logic does not know anything about this params, so it should not
        # introduce any changes to them by calling activate_interface()].
        tmp_trig_dict = self.get_trig()

        # Reset device
        self.reset()

        # Restore hardware settings which are not controlled by logic
        # but were changed by self._dev.reset()

        self.set_trig(src_str=tmp_trig_dict['src_str'],
                      slope_str=tmp_trig_dict['slope_str'])

        return 0

    # Output control

    def on(self):

        if self.get_mode() == 'sweep':
            self.reset_swp_pos()

        return self._cmd_wait(cmd_str=':OUTP:STAT ON')

    def off(self):
        return self._cmd_wait(cmd_str=':OUTP:STAT OFF')

    def get_status(self):
        status = int(self._dev.query('OUTP:STAT?'))
        return status

    # Power

    def get_pwr(self):
        return float(self._dev.query(':POW?'))

    def set_pwr(self, pwr):
        self._cmd_wait(':POW {0:f}'.format(pwr))

        return self.get_pwr()

    # Frequency

    def get_freq(self):
        mode = self.get_mode()

        if mode == 'cw':
            ret_val = float(self._dev.query(':FREQ?'))

        elif mode == 'sweep':
            start = float(self._dev.query(':FREQ:STAR?'))
            stop = float(self._dev.query(':FREQ:STOP?'))
            step = float(self._dev.query(':SWE:STEP?'))

            n_pts = int((stop - start) / step) + 2

            ret_val = dict(start=start, stop=stop, n_pts=n_pts)

        else:
            raise MWSrcError('get_freq(): got unknown mode {}'.format(mode))

        return ret_val

    def set_freq(self, freq):

        if self.get_status() == 1:
            self.off()

        # Activate CW mode
        self._cmd_wait(':FREQ:MODE CW')

        # Set CW frequency
        self._cmd_wait(':FREQ {0:f}'.format(freq))

        return self.get_freq()

    def set_freq_swp(self, start, stop, n_pts):

        if self.get_status() == 1:
            self.off()

        # Set mode to Sweep
        self._cmd_wait(':FREQ:MODE SWEEP')

        # Set frequency sweep
        step = (stop - start) / (n_pts - 1)

        self._cmd_wait(':SWE:MODE STEP')
        self._cmd_wait(':SWE:SPAC LIN')
        self._cmd_wait(':FREQ:START {0:f}'.format(start))
        self._cmd_wait(':FREQ:STOP {0:f}'.format(stop))
        self._cmd_wait(':SWE:STEP:LIN {0:f}'.format(step))

        return self.get_freq()

    def reset_swp_pos(self):
        """Reset of MW sweep mode position to start (start frequency)

        @return int: error code (0:OK, -1:error)
        """

        self._cmd_wait(':ABOR:SWE')
        return 0

    def get_mode(self):

        mode_str = self._dev.query(':FREQ:MODE?').strip('\n').lower()

        if 'cw' in mode_str:
            return 'cw'
        elif 'swe' in mode_str:
            return 'sweep'
        else:
            msg_str = 'get_mode(): unknown mode string {} was returned'
            self.log.error(msg_str=msg_str)
            raise MWSrcError(msg_str)

    # Technical methods

    def _cmd_wait(self, cmd_str):
        """Writes the command in command_str via resource manager
        and waits until the device has finished processing it.

        @param cmd_str: The command to be written
        """

        self._dev.write(cmd_str)

        # Block command queue until cmd_str execution is complete
        self._dev.write('*WAI')
        # Block Python process until cmd_str execution is complete
        self._dev.query('*OPC?')

        # Error check
        self._er_chk()

        return 0

    def _er_chk(self):
        # Block command queue until all previous commands are complete
        self._dev.write('*WAI')
        # Block Python process until all previous commands are complete
        self._dev.query('*OPC?')

        # Read all messages
        out_str = self._dev.query(':SYSTem:ERRor:ALL?')
        out_str += ','
        out_str += self._dev.query('SYST:SERR?')

        out_str = out_str.replace('\n', '')
        out_str = out_str.replace('\r', '')
        out_str = out_str.replace('"', '')

        out_list = out_str.split(',')

        # Collect all warns and errors
        er_list = []
        warn_list = []

        msg_n = int(len(out_list) / 2)

        for idx in range(msg_n):
            msg_code = int(out_list[2 * idx])

            if msg_code == 0:
                # No error
                continue

            elif msg_code > 0:
                # Warning
                warn_list.append(out_list[2 * idx + 1])

            else:
                # Error
                er_list.append(out_list[2 * idx + 1])

        # Construct Warn message string
        if len(warn_list) > 0:
            warn_str = ''
            for warn in warn_list:
                warn_str += (warn + ' \n')
            warn_str = warn_str.rstrip('\n')
            self.log.warn(msg_str=warn_str)

        # Construct Error message string
        if len(er_list) > 0:
            er_str = ''
            for er in er_list:
                er_str += (er + ' \n')
            er_str = er_str.rstrip('\n')
            self.log.error(msg_str=er_str)
            raise MWSrcError(er_str)

        return 0

    def get_trig(self):

        #
        # Get trigger source
        #

        src_str = self._dev.query('TRIG:FSW:SOUR?')

        if 'EXT' in src_str:
            src_str = 'ext'
        elif 'AUTO' in src_str:
            src_str = 'int'
        else:
            msg_str = 'get_trig(): unknown trigger source was returned "{}" \n' \
                      ''.format(src_str)
            self.log.error(msg_str=msg_str)
            raise MWSrcError(msg_str)

        #
        # Get edge slope
        #

        slope_str = self._dev.query(':TRIG1:SLOP?')

        if 'POS' in slope_str:
            slope_str = 'r'
        elif 'NEG' in slope_str:
            slope_str = 'f'
        else:
            msg_str = 'get_trig(): unknown slope was returned "{}" \n' \
                      ''.format(slope_str)
            self.log.error(msg_str=msg_str)
            raise MWSrcError(msg_str)

        return dict(src_str=src_str, slope_str=slope_str)

    def set_trig(self, src_str='ext', slope_str='r'):

        if self.get_status() == 1:
            self.off()

        #
        # Set trigger source
        #

        if src_str == 'ext':
            src_str = 'EXT'
        elif src_str == 'int':
            src_str = 'AUTO'
        else:
            msg_str = 'set_trig(): unknown trigger source  "{}" \n' \
                      'Valid values are "ext" - external, "int" - internal' \
                      ''.format(src_str)
            self.log.error(msg_str=msg_str)
            raise MWSrcError(msg_str)

        self._cmd_wait('TRIG:FSW:SOUR {}'.format(src_str))

        #
        # Set trigger edge
        #

        if slope_str == 'r':
            edge = 'POS'
        elif slope_str == 'f':
            edge = 'NEG'
        else:
            msg_str = 'set_trig(): invalid argument slope_str={} \n' \
                      'Valid values are: "r" - raising, "f" - falling' \
                      ''.format(slope_str)
            self.log.error(msg_str=msg_str)
            raise ValueError(msg_str)

        self._cmd_wait(':TRIG1:SLOP {0}'.format(edge))

        return self.get_trig()

    def force_trig(self):
        """ Trigger the next element in the list or sweep mode programmatically.

        @return int: error code (0:OK, -1:error)

        Ensure that the Frequency was set AFTER the function returns, or give
        the function at least a save waiting time.
        """

        self._cmd_wait('*TRG')

        return 0
Beispiel #15
0
class Driver():

    def reset(self):
        """ Create factory reset"""
        self.device.write('*RST')
        self.log.info("Reset to factory settings successfull.")

    def __init__(self, gpib_address, logger):
        """Instantiate driver class.

        :gpib_address: GPIB-address of the device, e.g. 'COM8'
            Can ba found in the Windows device manager.
        :logger: And instance of a LogClient.
        """

        # Instantiate log.
        self.log = LogHandler(logger=logger)

        self.rm = ResourceManager()

        try:
            self.device = self.rm.open_resource(gpib_address)
            self.device_id = self.device.query('*IDN?')
            self.log.info(f"Successfully connected to {self.device_id}.")
        except VisaIOError:
            self.log.error(f"Connection to {gpib_address} failed.")

        # Reset to factory settings.
        self.reset()

        # Read and store min and max power.
        self.power_min, self.power_max = [
            float(
                self.device.query(f'pow? {string}')
            )
            for string in ['min', 'max']
        ]

        # Read and store min and max frequency.
        self.freq_min, self.freq_max = [
            float(
                self.device.query(f'freq? {string}')
            )
            for string in ['min', 'max']
        ]

    def output_on(self):
        """ Turn output on."""

        self.device.write('OUTPut ON')
        self.log.info(f"Output of {self.device_id} turned on.")

    def output_off(self):
        """ Turn output off."""

        self.device.write('OUTP OFF')
        self.log.info(f"Output of {self.device_id} turned off.")

    def check_power_out_of_range(self):
        """ Returns True if current power is outside of calibration range, False otherwise"""

        if not self.is_output_on():
            warn = "Please enable output to check if calibration out of range"
            self.log.warn(warn)
            return warn

        # Query status register and do bitwise AND to check status of bit 3
        if int(self.device.query('Status:Questionable:Condition?')) & 8:
            power_out_of_range = True
        else:
            power_out_of_range = False

        return power_out_of_range

    def is_output_on(self):
        """Returns True if output is enabled, False otherwise."""
        return bool(int(self.device.query('OUTPut?')))

    def set_freq(self, freq):
        """ Set frequency (in Hz)

        :freq: Target frequency in Hz
        """
        if not self.freq_min <= freq <= self.freq_max:
            self.log.error(
                f"Frequency must be between {self.freq_min} Hz and {self.freq_max} Hz"
            )
        self.device.write(f'freq {freq}')
        self.log.info(f"Frequency of {self.device_id} set to {freq} Hz.")

    def get_freq(self):
        """Returns current frequency setting."""
        return float(self.device.query('freq?'))

    def set_power(self, power):
        """Set output power (in dBm)

        :power: Target power in dBm
        """

        if not self.power_min <= power <= self.power_max:
            self.log.error(
                f"Power must be between {self.power_min} dBm and {self.power_max} dBm"
            )

        self.device.write(f'pow {power}')
        self.log.info(f"Output power of {self.device_id} set to {power} dBm.")

    def get_power(self):
        """Returns current output power setting."""
        return float(self.device.query('pow?'))
Beispiel #16
0
class Driver:
    def __init__(self, gpib_address=None, logger=None):
        """Instantiate driver class.

        :gpib_address: GPIB-address of the scope, e.g. 'GPIB0::12::INSTR'
            Can be read out by using
                rm = pyvisa.ResourceManager()
                rm.list_resources()
        :logger: An instance of a LogClient.
        """

        # Instantiate log.
        self.log = LogHandler(logger=logger)

        self.rm = ResourceManager()

        try:
            self.device = self.rm.open_resource(gpib_address)
            device_id = self.device.query('*IDN?')
            self.log.info(f"Successfully connected to {device_id}.")
            # We set a more forgiving timeout of 10s (default: 2s).
            # self.device.timeout = 10000
        except VisaIOError:
            self.log.error(f"Connection to {gpib_address} failed.")

    def get_power(self, channel):
        """ Returns the current power in watts on a desired channel

        :param channel: (int) channel to read power of (either 1 or 2)
        :return: (float) power in watts
        """

        power = self.device.query(f':POW{channel}:VAL?')
        return float(power)

    def get_wavelength(self, channel):
        """ Returns the current wavelength in nm for the desired channel

        :param channel: (int) channel to read wavelength of
        :return: (int) wavelength
        """

        wavelength = self.device.query(f':WAVEL{channel}:VAL?')
        return int(float(wavelength))

    def get_range(self, channel):
        """ Returns the current power range for the channel

        :param channel: (int) channel to read range of
        :return: (str) range
        """

        pr = self.device.query(f':PRANGE{channel}?')
        return pr

    def set_wavelength(self, channel, wavelength):
        """ Sets the wavelength

        :param channel: (int) channel to set wavelength of
        """

        self.device.write(f':WAVEL{channel}:VAL {wavelength}')

    def set_range(self, channel, p_range):
        """ Sets the range

        :param channel: (int) channel to set range of
        :param p_range: (str) range string identifier, can be anything in
            'AUTO', 'R1NW', 'R10NW', 'R100NW', 'R1UW', 'R10UW', 'R100UW', 'R1MW',
            'R10MW', 'R100MW', 'R1W', 'R10W', 'R100W', 'R1KW'
        """

        self.device.write(f':PRANGE{channel} {p_range}')
class MSquaredWMServer:
    """ Server exposing the High Finesse Wave Meter to the M2 SolsTis control interface

    :ip: (str) IP adress the M2 interface is listening for a wavemeter connection
        (must be set accordingly in web interface).
    :port: (int) Port number the M2 interface is listening for a wavemeter connection
        (must be set accordingly in web interface) .
    :channel: (int) Wavemeter channel where SolTis is connected to.
    :log_client: (object) LogClient instance.
    :wavemeterclient: (object) Wavemeter client instance.
    :log_tcp: (bool) If True, log incoming and outgoing TCP packets.
    """
    def __init__(self, ip, port, channel, log_client, wavemeterclient,
                 log_tcp):
        self.ip = ip
        self.port = port
        self.log_tcp = log_tcp
        self.log = LogHandler(log_client)
        self.channel = channel
        self.trans_id = 512193
        self.wm = wavemeterclient

    def read_wavelength(self):
        return self.wm.get_wavelength(channel=self.channel,
                                      units="Wavelength (nm)")

    def start(self):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.addr = (self.ip, self.port)
        self.sock.bind(self.addr)
        self.sock.listen(1)
        self.log.info(f"Started server on {self.addr}")
        self.log.info(f"Initial wavelength {self.read_wavelength()}")
        self.loop()

    def loop(self):
        while True:
            conn, c_addr = self.sock.accept()
            try:
                self.log.info(f"Connection from {c_addr}")
                while True:
                    msg = conn.recv(1024)
                    if msg:
                        if self.log_tcp:
                            self.log.info(f"Recieved\n {msg} \n")
                        res = self.response(msg)
                        conn.sendall(res)
                        if self.log_tcp:
                            self.log.info(f"Sent\n {res} \n")
                    else:
                        break
            finally:
                conn.close()

    def response(self, msg):
        data = json.loads(msg)
        task = data['message']['transmission']['task1']['name']
        res = self.default_data()
        self.set_id(res, self.trans_id)
        self.trans_id += 1
        if task == 'start-link':
            self.set_task_name(res, 'start-link-reply')
            params = {'status': 'ok', 'ip-address': self.ip}
            self.set_task_params(res, params)
        elif task == 'get-wavelength':
            self.set_task_name(res, 'get-wavelength-reply')
            params = {
                'status': 'ok',
                'wavelength': [self.read_wavelength()],
                'mode': 'fixed',
                'channel': [self.channel],
                'calibration': 'inactive',
                'configuration': 'ok'
            }
            self.set_task_params(res, params)
        elif task == 'wlm-server-app':
            self.set_task_name(res, 'wlm-server-app-reply')
            self.set_task_id(res, self.get_task_id(data))
            params = {
                'status': 'ok',
            }
            self.set_task_params(res, params)
        elif task == 'check-wlm-server':
            self.set_task_name(res, 'check-wlm-server-reply')
            self.set_task_id(res, self.get_task_id(data))
            params = {
                'status': 'active',
            }
            self.set_task_params(res, params)
        elif task == 'configure-wlm':
            self.set_task_name(res, 'configure-wlm-reply')
            self.set_task_id(res, self.get_task_id(data))
            params = {
                'result-mode': 'ok',
                'exposure-mode': 'ok',
                'pulse-mode': 'ok',
                'precision': 'ok',
                'fast-mode': 'ok',
                'pid-p': 'failed',
                'pid-i': 'failed',
                'pid-d': 'failed',
                'pid-t': 'failed',
                'pid-dt': 'failed',
                'sensitivity-factor': 'failed',
                'use-ta': 'failed',
                'polarity': 'failed',
                'sensitivity-dimension': 'failed',
                'use-const-dt': 'failed',
                'auto-clear-history': 'failed'
            }
            self.set_task_params(res, params)
        elif task == 'set-measurement-op':
            self.set_task_name(res, 'set-measurement-op-reply')
            self.set_task_id(res, self.get_task_id(data))
            params = {'status': 'ok'}
            self.set_task_params(res, params)
        elif task == 'set-switch':
            self.set_task_name(res, 'set-switch-reply')
            self.set_task_id(res, self.get_task_id(data))
            params = {'status': 'ok'}
            self.set_task_params(res, params)
        elif task == 'set-exposure':
            self.set_task_name(res, 'set-exposure-reply')
            self.set_task_id(res, self.get_task_id(data))
            params = {'status': 'ok'}
            self.set_task_params(res, params)

        # If unhandled task, just add generic status:ok reply
        else:
            reply_task = f'{task}-reply'
            self.set_task_name(res, reply_task)
            self.set_task_id(res, self.get_task_id(data))
            params = {'status': 'ok'}
            self.set_task_params(res, params)

        return json.dumps(res).encode('ascii')

    def set_id(self, data, i):
        data['message']['transmission-id'] = [i]

    def set_task_name(self, data, name):
        data['message']['transmission']['task1']['name'] = name

    def get_task_id(self, data):
        return data['message']['transmission']['task1']['id'][0]

    def set_task_id(self, data, i):
        data['message']['transmission']['task1']['id'] = [i]

    def set_task_params(self, data, params):
        data['message']['transmission']['task1']['parameters'] = params

    def default_data(self):
        data = {
            'message': {
                'transmission-id': [0],
                'task-count': [1],
                'transmission': {
                    'task1': {
                        'name': '',
                        'id': [1],
                        'parameters': {}
                    }
                }
            }
        }
        return data
Beispiel #18
0
class ANC300:

    # compiled regular expression for finding numerical values in reply strings
    _reg_value = re.compile(r"\w+\s+=\s+(\w+)")

    def __init__(self,
                 host,
                 port=0,
                 query_delay=0.001,
                 passwd=None,
                 limits=DEFAULT_LIMITS,
                 logger=None):
        """ Instantiate ANC300 objcet.
        :param host: IP of telnet connection.
        :param port: Port of telnet connection.
        :param query_delay: Delay between queries (ins s).
        :param passwd: Telnet login password.
        :param limits: Voltage limit dictionary.
        :param logger: Log client.
        """

        self.log = LogHandler(logger)
        self.query_delay = query_delay
        self.lastcommand = ""
        self.freq_lim = limits["freq_lim"]
        self.step_voltage_lim = limits["step_voltage_lim"]

        # Instantiate Telnet Connection
        self.connection = Telnet(host, port)

        # Setup terminations
        self.read_termination = '\r\n'
        self.write_termination = self.read_termination

        # Log into telnet client
        time.sleep(query_delay)
        ret = self._read(check_ack=False)
        self._write(passwd, check_ack=False)
        time.sleep(self.query_delay)
        ret = self._read(check_ack=False)
        authmsg = ret.split(self.read_termination)[1]

        if authmsg != 'Authorization success':
            self.log.error(f"Attocube authorization failed '{authmsg}'")
        else:

            # Read board serial number
            board_ver = self._write("getcser")

            # Check how many exes are available
            valid_axes, num_axes = self._check_num_channels()
            self.axes = valid_axes
            self.num_axes = num_axes

            self.log.info(
                f"Connected to {board_ver} with {self.num_axes} available axes."
            )

    def _check_num_channels(self):
        """ Checks how many axes are available

        :returns: valid_axis, list containing the axis indices (1-indexed), num_axis, integer
        """

        valid_axis = []
        for i in range(1, 8):
            axis_serial = self._write(f"getser {i}", check_axes=True)
            if axis_serial != 'Wrong axis type':
                valid_axis.append(i)

        num_axis = len(valid_axis)
        return valid_axis, num_axis

    def channel_valid(self, channel):
        """ Checks if channel number matches total number of detected channels.

        :returns: channel_valid, boolean. If true, the channel is valid.
        """

        channel_valid = False
        if channel not in self.axes:
            self.log.error(
                f"Channel {channel} not valid, available channels are {self.axes}."
            )
        else:
            channel_valid = True
        return channel_valid

    def _check_acknowledgement(self, reply, msg=""):
        """ checks the last reply of the instrument to be 'OK', a log error is raised

        :param reply: last reply string of the instrument
        :param msg: optional message for the eventual error
        """
        if reply != 'OK':
            if msg == "":  # clear buffer
                msg = reply
                self._read()
            self.log.error("AttocubeConsoleAdapter: Error after command "
                           f"{self.lastcommand} with message {msg}")

    def _read(self, check_ack=True, check_axes=False):
        """ Reads a reply of the instrument which consists of two or more
        lines. The first ones are the reply to the command while the last one
        is 'OK' or 'ERROR' to indicate any problem. In case the reply is not OK
        a ValueError is raised.
        :param check_axes: Supressed error message (only for check axis command).
        :returns: Cleaned up response of the instruments (stripped by the status indicator
        and initial command).
        """
        time.sleep(self.query_delay)
        ret = self.connection.read_some().decode() + \
            self.connection.read_very_eager().decode()

        raw = ret.strip(self.read_termination)

        if check_ack:
            check = ""
            split_return = raw.rsplit(sep='\n')
            if len(split_return) == 3:
                # No argument returned
                check = split_return[-2].strip("\r")
                ret = split_return[0].strip("\r")
            elif len(split_return) == 4:
                check = split_return[-2].strip("\r")
                ret = split_return[1].strip("\r")

            if not check_axes:
                self._check_acknowledgement(check, ret)

        return ret

    def _extract_value(self, reply):
        """ preprocess_reply function for the Attocube console. This function
        tries to extract <value> from 'name = <value> [unit]'. If <value> can
        not be identified the original string is returned.
        :param reply: reply string
        :returns: float with only the numerical value, or the original string
        """
        r = self._reg_value.search(reply)
        if r:
            return r.groups()[0]
        else:
            return reply

    def _write(self, command, check_ack=True, check_axes=False):
        """ Writes a command to the instrument
        :param command: command string to be sent to the instrument
        :param check_ack: boolean flag to decide if the acknowledgement is read
            back from the instrument. This should be True for set pure commands
            and False otherwise.
        :param check_axes: Supressed error message (only for check axis command).
        :return: Returns cleaned up intrument response if check_ack is chosen,
        'None' otherwise.
        """
        time.sleep(self.query_delay)
        self.lastcommand = command
        command = command + self.write_termination
        self.connection.write(command.encode())

        if check_ack:
            reply = self._read(check_ack=check_ack, check_axes=check_axes)
        else:
            reply = None
        return reply

    @check_channel
    def _set_mode(self, channel, mode):
        """ Set mode of controller
        :param channel: (int) index of channel from 1 to self.num_ch

        :param mode: String indicating mode, which can be gnd (grounded), cap,
        (capacitance measurement), and stp (Step mode)
        """
        self._write(f"setm {str(channel)} {str(mode)}")

    @check_channel
    def ground(self, channel):
        """ Grounds channel

        :param channel: (int) index of channel from 1 to self.num_ch
        """
        self._set_mode(channel, 'gnd')
        self.log.info(f"Grounded channel {channel}.")

    @check_channel
    def set_parameters(self,
                       channel,
                       mode=None,
                       frequency=None,
                       amplitude=None):
        """ Sets parameters for motion

        Leave parameter as None in order to leave un-changed

        :param channel: (int) index of channel from 1 to self.num_ch
        :param mode: (str) default is 'step',
        :param freq: (int) frequency in Hz from 1 to 20000
        :param amp: (float) amplitude in volts from 0 to 100
        """

    @check_channel
    def get_step_voltage(self, channel):
        """ Returns the step voltage in V

        :param channel: (int) channel index (from 1)
        """
        return self._extract_value(self._write(f"getv {str(channel)}"))

    @check_channel
    def set_step_voltage(self, channel, voltage=30):
        """ Sets the step voltage to the piezo

        :param channel: (int) channel index from 1 to self.num_ch
        :param voltage: (float) voltage to set from 0 to 150 V (default is 30)
        """

        if not (0 <= voltage <= self.step_voltage_lim):
            self.log.error(
                f"Step voltage has to be between 0 V and {self.step_voltage_lim} V."
            )
            return

        self._write(f"setv {str(channel)} {str(voltage)}")
        self.log.info(
            f"Change step voltage of channel {channel} to {voltage} V.")

    @check_channel
    def get_step_frequency(self, channel):
        """ Returns the step frequency on channel

        :param channel: (int) channel index from 1 to self.num_ch
        """

        return self._extract_value(self._write(f"getf {str(channel)}"))

    @check_channel
    def set_step_frequency(self, channel, freq=1000):
        """ Sets the step voltage to the piezo

        :param channel: (int) channel index from 1 to self.num_ch
        :param voltage: (float) voltage to set from 0 to 10000 Hz (default is 1000 Hz)
        """

        if not (0 <= freq <= self.freq_lim):
            self.log.error(
                f"Frequency has to be between 0 Hz and {self.freq_lim} Hz")
            return

        self._write(f"setf {str(channel)} {str(freq)}")
        self.log.info(
            f"Change step frequency of channel {channel} to {freq} Hz.")

    @check_channel
    def n_steps(self, channel, n=1):
        """ Takes n steps

        :param channel: (int) channel index from 1 to self.num_ch
        :param n: (int) number of steps to take, negative is in opposite direction
        """

        # Set into stepping mode
        self._set_mode(channel, 'stp')

        if n > 0:
            self._write(f"stepu {str(channel)} {str(n)}", check_ack=False)
        else:
            self._write(f"stepd {str(channel)} {str(abs(n))}", check_ack=False)

        self.log.info(f"Took {n} steps on channel {channel}.")

    @check_channel
    def get_capacitance(self, channel):
        """ Measures capacitance of positioner

        :param channel: (int) channel index from 1 to self.num_ch
        :return: Returns C in nF
        """

        # Set into stepping mode
        self._set_mode(channel, 'cap')
        time.sleep(1)
        cap = float(self._extract_value(self._write(f"getc {str(channel)}")))
        self.log.info(f"Capacitance measured on chanel {channel}: {cap} nF.")
        return cap

    @check_channel
    def get_output_voltage(self, channel):
        """ Get output voltage

        :param channel: (int) channel index from 1 to self.num_ch
        :return: Returns step voltage in volt
        """
        return float(self._extract_value(self._write(f"geto {str(channel)}")))

    @check_channel
    def move(self, channel, backward=False):
        """ Moves continously

        :param channel: (int) channel index from 1 to self.num_ch
        :param backward: (bool) whether or not to step in backwards direction (default False)
        """
        # Set into stepping mode
        self._set_mode(channel, 'stp')

        if not backward:
            self._write(f"stepu {str(channel)} c", check_ack=False)
        else:
            self._write(f"stepd {str(channel)} c", check_ack=False)

    @check_channel
    def stop(self, channel):
        """ Terminates any ongoing movement

        :param channel: (int) channel index from 1 to self.num_ch
        """
        self._write(f"stop {str(channel)}")
        self.log.info(f"Stopped channel {channel}.")

    @check_channel
    def is_moving(self, channel):
        """ Returns whether or not the positioner is moving

        :param channel: (int) channel index from 1 to self.num_ch

        :return: (bool) true if moving
        """
        output_voltage = self.get_output_voltage(channel)

        if output_voltage < 1E-3:
            return False
        else:
            return True

    def stop_all(self):
        """ Terminates any ongoing movement on all axes"""

        for i in self.axes:
            self.stop(i)

    def ground_all(self):
        """ Grounds all positioners"""

        for i in self.axes:
            self.ground(i)
Beispiel #19
0
class Controller:
    """ A script class for controlling MCS2 positioners + interfacing with GUI"""

    NUM_CHANNELS = 9
    WIDGET_DICT = dict(step_left=NUM_CHANNELS,
                       step_right=NUM_CHANNELS,
                       walk_left=NUM_CHANNELS,
                       walk_right=NUM_CHANNELS,
                       n_steps=NUM_CHANNELS,
                       is_moving=NUM_CHANNELS,
                       amplitude=NUM_CHANNELS,
                       frequency=NUM_CHANNELS,
                       velocity=NUM_CHANNELS,
                       voltage=NUM_CHANNELS,
                       lock_button=int(NUM_CHANNELS / 3),
                       keyboard_change_combo=1)
    DC_TOLERANCE = 0.1
    AXIS_ORDER = [[4, 3, 7], [6, 1, 5], [8, 0, 2]]

    def __init__(self,
                 nanopos_client: smaract_mcs2.Client,
                 gui='positioner_control',
                 log_client=None,
                 config=None,
                 port=None):
        """ Instantiates the controller

        :param nanopos_client: (pylabnet.network.client_server.smaract_mcs2.Client)
        :param gui: name of .ui file ot use
        :param log_client: (pylabnet.utils.logging.logger.LogClient)
        :param config: (str) name of config file, optional
        :param port: (int) port number for update/script server
        """

        self.pos = nanopos_client
        self.log = LogHandler(logger=log_client)
        self.gui = Window(gui_template=gui, host=get_ip(), port=port)
        self.gui.apply_stylesheet()

        self.widgets = get_gui_widgets(self.gui, **self.WIDGET_DICT)
        self.save_params = generate_widgets(
            dict(n_steps=self.NUM_CHANNELS,
                 amplitude=self.NUM_CHANNELS,
                 frequency=self.NUM_CHANNELS,
                 velocity=self.NUM_CHANNELS))

        # Additional attributes
        self.prev_amplitude = [50] * self.NUM_CHANNELS
        self.prev_frequency = [30] * self.NUM_CHANNELS
        self.prev_velocity = [100] * self.NUM_CHANNELS
        self.prev_voltage = [50] * self.NUM_CHANNELS
        self.voltage_override = False
        self.config = config
        self.lock_status = [False] * int(self.NUM_CHANNELS / 3)
        self.released = [False] * self.NUM_CHANNELS
        self.gui.config_label.setText(self.config)

        # Configure all button and parameter updates
        self._setup_gui()

        # Setup shortcut to use keyboard to step fiber
        self.press_right = QShortcut(QKeySequence('Right'), self.gui)
        self.press_left = QShortcut(QKeySequence('Left'), self.gui)
        self.press_up = QShortcut(QKeySequence('Up'), self.gui)
        self.press_down = QShortcut(QKeySequence('Down'), self.gui)
        self.press_up_z = QShortcut(QKeySequence('PgUp'), self.gui)
        self.press_down_z = QShortcut(QKeySequence('PgDown'), self.gui)

        self.widgets['keyboard_change_combo'].currentIndexChanged.connect(
            self._bind_arrow_keys)

    def _disconnect_arrow_keys(self):
        """ Unbinds the arrow, up/down keys from any actions."""
        self.press_right.activated.disconnect()
        self.press_left.activated.disconnect()
        self.press_up.activated.disconnect()
        self.press_down.activated.disconnect()
        self.press_up_z.activated.disconnect()
        self.press_down_z.activated.disconnect()

    def _bind_arrow_keys(self):
        """ Binds arroy keys on keyboard to step around front fiber."""

        try:
            self._disconnect_arrow_keys()
        except TypeError:
            self.log.info('Initial call of arrowkey binding.')

        binding_index = self.widgets['keyboard_change_combo'].currentIndex()

        front_names = [
            'step_left', 'step_right', 'step_left', 'step_right', 'step_right',
            'step_left'
        ]
        front_index = [6, 6, 1, 1, 5, 5]

        rear_names = [
            'step_right', 'step_left', 'step_right', 'step_left', 'step_right',
            'step_left'
        ]
        rear_index = [8, 8, 0, 0, 2, 2]

        if binding_index == 0:
            return
        if binding_index == 1:
            names = front_names
            index = front_index
        elif binding_index == 2:
            names = rear_names
            index = rear_index

        self.press_right.activated.connect(
            lambda: self.widgets[names[0]][index[0]].animateClick())
        self.press_left.activated.connect(
            lambda: self.widgets[names[1]][index[1]].animateClick())
        self.press_up.activated.connect(
            lambda: self.widgets[names[2]][index[2]].animateClick())
        self.press_down.activated.connect(
            lambda: self.widgets[names[3]][index[3]].animateClick())
        self.press_up_z.activated.connect(
            lambda: self.widgets[names[4]][index[4]].animateClick())
        self.press_down_z.activated.connect(
            lambda: self.widgets[names[5]][index[5]].animateClick())

    def initialize_parameters(self, channel, params):
        """ Initializes all parameters to values given by params, except for DC voltage

        :param channel: (int) channel index (from 0)
        :param params: (tuple) params in order n_steps, is_moving, amplitude, frequency, velocity,
            voltage
        """

        self.pos.set_parameters(channel, amplitude=params[2])
        self.pos.set_parameters(channel, frequency=params[3])
        self.pos.set_parameters(channel, dc_vel=params[4])

        # Measure DC voltage and set it in the GUI
        self._set_voltage_display(channel)

    def get_GUI_parameters(self, channel):
        """ Gets the current GUI parameters for a given channel

        :param channel: (int) channel index (from 0)

        :return: (tuple) params in order n_steps, is_moving, amplitude, frequency, velocity, voltage
        """

        return (self.widgets['n_steps'][channel].value(),
                self.widgets['is_moving'][channel].isChecked(),
                self.widgets['amplitude'][channel].value(),
                self.widgets['frequency'][channel].value(),
                self.widgets['velocity'][channel].value(),
                self.widgets['voltage'][channel].value())

    def stop_all(self):
        """ Stops all channels """

        for channel in range(self.NUM_CHANNELS):
            self.pos.stop(channel)

    def load_settings(self):
        """ Loads settings from configuration """
        self.gui.load_gui("mcs2_control",
                          self.gui.config_label.text(),
                          logger=self.log)

    # Technical methods

    def _set_voltage_display(self, channel):
        """ Sets the voltage on the GUI to the current value measured by the controller

        :param channel: (int) channel index (from 0)
        """

        voltage = self.pos.get_voltage(channel)
        self.prev_voltage[channel] = voltage
        self.widgets['voltage'][channel].setValue(voltage)
        self.voltage_override = True

    def save(self):
        """Saves or loads settings if relevant"""

        scalars = []
        for save_item in self.save_params:
            scalars += save_item
        self.gui.save_gui(self.gui.get_text('config_label'),
                          logger=self.log,
                          scalars=scalars)

    def _lock_stack(self, stack: int):
        """ Locks/unlocks a particular stack"""

        if self.lock_status[stack]:
            self.lock_status[stack] = False
            self.widgets['lock_button'][stack].setStyleSheet(
                'background-color:rgb(100,0,0)')
            self.widgets['lock_button'][stack].setText('Lock')
        else:
            self.lock_status[stack] = True
            self.widgets['lock_button'][stack].setStyleSheet(
                'background-color:rgb(0,100,0)')
            self.widgets['lock_button'][stack].setText('Unlock')

    def _is_axis_locked(self, channel: int):
        """ Checks if an axis is locked and returns boolean

        :param channel: (int) axis to check
        :return: (bool) whether or not the axis is currently locked
        """

        for index, ordering in enumerate(self.AXIS_ORDER):
            if channel in ordering:
                locked = self.lock_status[index]

        return locked

    def _step_left(self, channel: int):
        """ Steps a particular channel if unlocked

        :param channel: (int) channel to step
        """

        if not self._is_axis_locked(channel):

            self.pos.n_steps(channel=channel,
                             n=-self.widgets['n_steps'][channel].value())
            self.widgets['step_left'][channel].setStyleSheet(
                'background-color:red')

            if self.pos.is_moving(channel):

                while self.pos.is_moving(channel):
                    self.widgets['is_moving'][channel].setCheckable(True)
                    self.widgets['is_moving'][channel].setChecked(True)
                    self.gui.force_update()
                self.widgets['is_moving'][channel].setChecked(False)
                self.widgets['is_moving'][channel].setCheckable(False)

            self.widgets['step_left'][channel].setStyleSheet(
                'background-color:black')

            # Ugly hack to set DC voltage of Front X to 0
            self.log.info(f"Channel {channel}")
            if channel in BROKEN_CHANNELS:
                self._update_voltage(channel, 0)

            self._set_voltage_display(channel)
        else:
            self.log.info("LOCKED")

    def _step_right(self, channel: int):
        """ Steps a particular channel if unlocked

        :param channel: (int) channel to step
        """

        if not self._is_axis_locked(channel):

            self.pos.n_steps(channel=channel,
                             n=self.widgets['n_steps'][channel].value())
            self.widgets['step_right'][channel].setStyleSheet(
                'background-color:red')

            if self.pos.is_moving(channel):

                while self.pos.is_moving(channel):
                    self.widgets['is_moving'][channel].setCheckable(True)
                    self.widgets['is_moving'][channel].setChecked(True)
                    self.gui.force_update()
                self.widgets['is_moving'][channel].setChecked(False)
                self.widgets['is_moving'][channel].setCheckable(False)

            self.widgets['step_right'][channel].setStyleSheet(
                'background-color:black')

            # Ugly hack to set DC voltage of Front X to 0
            if channel in BROKEN_CHANNELS:
                self._update_voltage(channel, 0)

            self._set_voltage_display(channel)

    def _walk_left(self, channel: int):

        if not self._is_axis_locked(channel):

            if not self.pos.is_moving(channel):
                self.widgets['walk_left'][channel].setStyleSheet(
                    'background-color:red')
                self.pos.move(channel, backward=True)

                while self.pos.is_moving(channel):
                    self.widgets['is_moving'][channel].setCheckable(True)
                    self.widgets['is_moving'][channel].setChecked(True)
                    self.gui.force_update()
                self.widgets['is_moving'][channel].setChecked(False)
                self.widgets['is_moving'][channel].setCheckable(False)

                if not self.widgets['walk_left'][channel].isDown():
                    self.widgets['walk_left'][channel].setStyleSheet(
                        'background-color:black')

                    self._set_voltage_display(channel)

    def _walk_right(self, channel: int):

        if not self._is_axis_locked(channel):

            if not self.pos.is_moving(channel):
                self.widgets['walk_right'][channel].setStyleSheet(
                    'background-color:red')
                self.pos.move(channel, backward=False)

                while self.pos.is_moving(channel):
                    self.widgets['is_moving'][channel].setCheckable(True)
                    self.widgets['is_moving'][channel].setChecked(True)
                    self.gui.force_update()
                self.widgets['is_moving'][channel].setChecked(False)
                self.widgets['is_moving'][channel].setCheckable(False)

                if not self.widgets['walk_right'][channel].isDown():
                    self.widgets['walk_right'][channel].setStyleSheet(
                        'background-color:black')

                    self._set_voltage_display(channel)

    def _update_voltage(self, channel: int, voltage: float):
        """ Updates the channels DC voltage

        :param channel: (int) channel to update
        :param voltage: (float) value of voltage to update ot
        """

        # If locked, get the current voltage and reset the GUI value to it
        if self._is_axis_locked(channel):
            self.widgets['voltage'][channel].setValue(
                self.pos.get_voltage(channel))

        # Otherwise set the DC voltage
        else:
            self.pos.set_voltage(channel, voltage)

            if self.pos.is_moving(channel):
                while self.pos.is_moving(channel):
                    self.widgets['is_moving'][channel].setCheckable(True)
                    self.widgets['is_moving'][channel].setChecked(True)
                    self.gui.force_update()
                self.widgets['is_moving'][channel].setChecked(False)
                self.widgets['is_moving'][channel].setCheckable(False)

    def _setup_gui(self):
        """ Configures what all buttons do """

        self.gui.load_button.clicked.connect(self.load_settings)
        self.gui.save_button.clicked.connect(self.save)
        self.gui.emergency_button.clicked.connect(self.stop_all)

        # Stack based items (common to 3 axes)
        for stack in range(int(self.NUM_CHANNELS / 3)):

            stack_no = copy.deepcopy(stack)

            # Lock button
            self.widgets['lock_button'][stack].pressed.connect(
                lambda stack=stack_no: self._lock_stack(stack))

        for channel in range(self.NUM_CHANNELS):

            channel_no = copy.deepcopy(channel)

            # Step buttons
            self.widgets['step_left'][channel_no].pressed.connect(
                lambda channel=channel_no: self._step_left(channel))
            self.widgets['step_right'][channel_no].pressed.connect(
                lambda channel=channel_no: self._step_right(channel))

            # Walk buttons
            self.widgets['walk_left'][channel_no].pressed.connect(
                lambda channel=channel_no: self._walk_left(channel))
            self.widgets['walk_left'][channel_no].released.connect(
                lambda channel=channel_no: self.pos.stop(channel))
            self.widgets['walk_right'][channel_no].pressed.connect(
                lambda channel=channel_no: self._walk_right(channel))
            self.widgets['walk_right'][channel_no].released.connect(
                lambda channel=channel_no: self.pos.stop(channel))

            # Parameters
            self.widgets['voltage'][channel_no].valueChanged.connect(
                lambda state, channel=channel_no: self._update_voltage(
                    channel=channel, voltage=state))
            self.widgets['amplitude'][channel_no].valueChanged.connect(
                lambda state, channel=channel_no: self.pos.set_parameters(
                    channel=channel, amplitude=state))
            self.widgets['frequency'][channel_no].valueChanged.connect(
                lambda state, channel=channel_no: self.pos.set_parameters(
                    channel=channel, frequency=state))
            self.widgets['velocity'][channel_no].valueChanged.connect(
                lambda state, channel=channel_no: self.pos.set_parameters(
                    channel=channel, dc_vel=state))
Beispiel #20
0
class Wrap(GatedCtrInterface):
    def __init__(self, tagger, click_ch, gate_ch, logger=None):
        """Instantiate gated counter

        :param tagger: instance of TimeTagger class
        :param click_ch: (int|list of int) clicks on all specified channels
                                    will be summed into one logical channel
        :param gate_ch: (int) positive/negative channel number - count while
                             gate is high/low
        """

        # Log
        self.log = LogHandler(logger=logger)

        # Reference to tagger
        self._tagger = tagger

        # Log device ID information to demonstrate that connection indeed works
        serial = self._tagger.getSerial()
        model = self._tagger.getModel()
        self.log.info(
            'Got reference to Swabian Instruments TimeTagger device \n'
            'Serial number: {0}, Model: {1}'
            ''.format(serial, model))

        # Gated Counter
        # reference to the TT.CountBetweenMarkers measurement instance
        self._ctr = None
        # number of count bins:
        #   length of returned 1D count array, the expected number of gate pulses,
        #   the size of allocated memory buffer.
        # must be given as argument of init_ctr() call
        self._bin_number = 0

        # Channel assignments
        self._click_ch = 0
        self._gate_ch = 0
        # reference to Combiner object
        #   (if _click_ch is a list - then counts on all channels are summed
        #   into virtual channel - self._combiner.getChannel())
        self._combiner = None
        # apply channel assignment
        self.set_ch_assignment(click_ch=click_ch, gate_ch=gate_ch)

        # Module status code
        #  -1 "void"
        #   0 "idle"
        #   1 "in_progress"
        #   2 "finished"
        self._status = -1
        self._set_status(-1)

        # Once __init__() call is complete,
        # the counter is ready to be initialized by the above-lying logic though init_ctr() call

    # ---------------- Interface ---------------------------

    def activate_interface(self):
        return 0

    def init_ctr(self, bin_number, gate_type):

        # Device-specific fix explanation:
        #
        #   CountBetweenMarkers measurement configured for n_value bins
        #   indeed fills-up buffer after n_value gate pules, but call of
        #   self._ctr.ready() still gives False. Only after one additional
        #   gate pulse it gives True, such that self.get_status()
        #   gives 2 and self.get_count_ar() returns:
        #
        #       device always needs an additional pulse to complete
        #       (even for n_values = 1 it needs 2 gate pulses).
        #
        #   Since this is very counter-intuitive and confuses
        #   above-lying logic, a fix is made here:
        #
        #       For given bin_number, CountBetweenMarkers measurement
        #       is instantiated with n_values = (bin_number - 1) such
        #       that it completes after receiving bin_number physical
        #       gate pulses as expected.
        #
        #       As a result, in the returned count_ar
        #       (still of length bin_number, as logic expects), the last
        #       value is just a copy of (bin_number - 1)-st element.
        #
        #   The last physical bin is not actually measured, what can lead
        #   to confusions when bin_number is on the order of one.
        #   The warning below reminds about it:
        if bin_number <= 5:
            self.log.warn(
                'init_ctr(): due to strange behaviour of TT.CountBetweenMarkers '
                'measurement, this driver makes a hack: counter is configured to '
                'measure bin_number-1 pulses and the last element of the returned '
                'count_ar is just a copy of the preceding one. \n'
                'With bin_number={}, only the first {} gate windows will actually be '
                'measured.'
                ''.format(bin_number, bin_number - 1))

        # Close existing counter, if it was initialized before
        if self.get_status() != -1:
            self.close_ctr()

        # Instantiate counter measurement
        try:
            if gate_type == 'RF':
                self._ctr = TT.CountBetweenMarkers(
                    tagger=self._tagger,
                    click_channel=self._click_ch,
                    begin_channel=self._gate_ch,
                    end_channel=-self._gate_ch,
                    n_values=bin_number - 1)
            elif gate_type == 'RR':
                self._ctr = TT.CountBetweenMarkers(
                    tagger=self._tagger,
                    click_channel=self._click_ch,
                    begin_channel=self._gate_ch,
                    n_values=bin_number - 1)
            else:
                msg_str = 'init_ctr(): unknown gate type "{}" \n' \
                          'Valid types are: \v' \
                          '     "RR" - Raising-Raising \n' \
                          '     "RF" - Raising-Falling'
                self.log.error(msg_str=msg_str)
                raise CtrError(msg_str)

            # set status to "idle"
            self._set_status(0)

            # save bin_number in internal variable
            self._bin_number = bin_number

        # handle NotImplementedError (typical error, produced by TT functions)
        except NotImplementedError:
            # remove reference to the counter measurement
            self._ctr = None
            # set status to "void"
            self._set_status(-1)

            msg_str = 'init_ctr(): instantiation of CountBetweenMarkers measurement failed'
            self.log.error(msg_str=msg_str)
            raise CtrError(msg_str)

        # Prepare counter to be started by start_counting()
        # (CountBetweenMarkers measurement starts running immediately after instantiation,
        # so it is necessary to stop it and erase all counts collected between instantiation and stop() call)
        self._ctr.stop()
        self._ctr.clear()

        return 0

    def close_ctr(self):

        # Try to stop and to clear TT.CountBetweenMarkers measurement instance
        try:
            self._ctr.stop()
            self._ctr.clear()
        except:
            pass

        # Remove reference, set status to "void"
        self._ctr = None
        self._set_status(-1)

        return 0

    def start_counting(self):

        current_status = self.get_status()

        # Sanity check: ensure that counter is not "void"
        if current_status == -1:
            msg_str = 'start_counting(): ' \
                      'counter is in "void" state - it ether was not initialized or was closed. \n' \
                      'Initialize it by calling init_ctr()'
            self.log.error(msg_str=msg_str)
            raise CtrError(msg_str)

        # Terminate counting if it is already running
        if current_status == 1:
            self.terminate_counting()

        # Try stopping and restarting counter measurement
        try:
            self._ctr.stop(
            )  # does not fail even if the measurement is not running
            self._ctr.clear()
            self._ctr.start()

            # set status to "in_progress"
            self._set_status(1)

            # Wait until the counter is actually ready to count
            time.sleep(0.1)

            return 0

        # handle exception in TT function calls [NotImplementedError]
        except NotImplementedError:
            # Since stop() and clear() methods are very robust,
            # this part is only executed if counter is totally broken.
            # In this case it makes sense to close counter.
            self.close_ctr()

            msg_str = 'start_counting(): call failed. Counter was closed. \n'\
                      'Re-initialize counter by calling init_ctr() again'
            self.log.error(msg_str=msg_str)
            raise CtrError(msg_str)

    def terminate_counting(self):

        # Action of this method is non-trivial for "in_progress" state only
        if self.get_status() != 1:
            return 0

        # Try stopping and clearing counter measurement
        try:
            # stop counter, clear count array
            self._ctr.stop()
            self._ctr.clear()

            # set status to "idle"
            self._set_status(0)
            return 0

        # handle exception in TT.stop()/TT.clear()
        except NotImplementedError:
            # Since stop() and clear() methods are very robust,
            # this part is only executed if counter is totally broken.
            # In this case it makes sense to close counter.
            self.close_ctr()

            msg_str = 'terminate_counting(): call failed. Counter was closed. \n' \
                      'Re-initialize it by calling init_ctr()'
            self.log.error(msg_str=msg_str)
            raise CtrError(msg_str)

    def get_status(self):

        # Check that counter measurement was initialized and that the connection works
        # by calling isRunning()
        #  -- if self._ctr is None or if connection is broken, call will rise some
        #     exception. In this case "void" status should be set
        #  -- if counter was initialized and connection works, it will return successfully
        #     (True or False, but the result does not matter)
        #     and further choice between "idle", "in_progress", and "finished" should be made
        try:
            self._ctr.isRunning()
        except:
            # set status to "void"
            self._status = -1

        # No handling of "idle" and "finished" status is needed:
        # it will be returned by self._status as is

        # Handle "in_progress" status
        #   This status means that measurement was started before.
        #   Now one needs to check if it is already finished or not.
        #   If measurement is complete, change status to "finished".
        if self._status == 1:
            if self._ctr.ready():

                self._ctr.stop()
                self._status = 2

        return copy.deepcopy(self._status)

    def get_count_ar(self, timeout=-1):

        # If current status is "in_progress",
        # wait for transition to some other state:
        #   "finished" if measurement completes successfully,
        #   "idle" if measurement is terminated,
        #   "void" if counter breaks
        start_time = time.time()
        sleep_time = abs(timeout) / 100

        while self.get_status() == 1:
            # stop waiting if timeout elapses
            if time.time() - start_time > timeout >= 0:
                break
            time.sleep(sleep_time)

        # Analyze current status and return correspondingly
        status = self.get_status()

        # return data only in the case of "finished" state
        if status == 2:
            count_array = np.array(self._ctr.getData(), dtype=np.uint32)

            # Fix of the issue with an additional gate pulse needed to complete
            # measurement (see comment in init_ctr() for explanation):
            #   the last element of returned array is just a copy
            #   of the last physically measured bin
            count_array = np.append(count_array, count_array[-1])

            return count_array

        # return empty list for all other states ("in_progress", "idle", and "void")
        else:
            if status == 1:
                self.log.warn(
                    'get_count_ar(): operation timed out, but counter is still running. \n'
                    'Try calling get_count_ar() later or terminate process by terminate_counting().'
                )
            elif status == 0:
                self.log.warn(
                    'get_count_ar(): counter is "idle" - nothing to read')
            else:
                msg_str = 'get_count_ar(): counter broke and was deleted \n' \
                          'Re-initialize it by calling init_ctr()'
                self.log.error(msg_str=msg_str)
                raise CtrError(msg_str)

            return []

    # ------------------------------------------------------

    def _set_status(self, new_status):
        """Method to set new status in a clean way.

        This method compares the requested new_status with current status
        and checks if this transition is possible. If transition is possible,
        the change is applied to self._status. Otherwise, no status change
        is applied, -1 is returned, and error message is logged.


        :param new_status: (int) new status value
                            -1 - "void"
                             0 - "idle"
                             1 - "in_progress"
                             2 - "finished"

        :return: (int) operation status code:
                 0 - OK, change was accepted and applied
                -1 - Error, impossible transition was requested,
                     no state change was applied
        """

        # Transition to "void" is always possible
        # by calling close_ctr()
        if new_status == -1:
            self._status = -1
            return 0

        # Transition to "idle" is possible from
        #   "void" by calling init_ctr()
        #   "in_progress" by calling terminate_counting()
        if new_status == 0:
            if self._status == -1 or self._status == 1:
                self._status = 0
                return 0
            else:
                msg_str = '_set_status(): transition to new_status={0} from self._status={1} is impossible. \n'\
                          'Counter status was not changed.'\
                          ''.format(new_status, self._status)
                self.log.error(msg_str=msg_str)
                raise CtrError(msg_str)

        # Transition to "in_progress" is possible from
        #   "idle" by calling start_counting()
        #   "finished" by calling start_counting()
        if new_status == 1:
            if self._status == 0 or self._status == 2:
                self._status = 1
                return 0
            else:
                msg_str = '_set_status(): transition to new_status={0} from self._status={1} is impossible. \n'\
                          'Counter status was not changed.'\
                          ''.format(new_status, self._status)
                self.log.error(msg_str=msg_str)
                raise CtrError(msg_str)

        # Transition to "finished" is only possible from "in_progress"
        # by successful completion of count_array accumulation
        if new_status == 2:
            if self._status == 1:
                self._status = 2
                return 0
            else:
                msg_str = '_set_status(): transition to new_status={0} from self._status={1} is impossible. \n'\
                          'Counter status was not changed.'\
                          ''.format(new_status, self._status)
                self.log.error(msg_str=msg_str)
                raise CtrError(msg_str)

    def get_ch_assignment(self):
        """Returns dictionary containing current channel assignment:
            {
                'click_ch': (int) click_channel_number_including_edge_sign
                'gate_ch': (int) gate_channel_number_including_edge_sign
            }

        :return: dict('click_ch': _, 'gate_ch': _)
        """

        click_ch = copy.deepcopy(self._click_ch)
        gate_ch = copy.deepcopy(self._gate_ch)

        return dict(click_ch=click_ch, gate_ch=gate_ch)

    def set_ch_assignment(self, click_ch=None, gate_ch=None):
        """Sets click channel and and gate channel.

        This method only changes internal variables
        self._click_ch and self._gate_ch.
        To apply the channel update, call  init_ctr() again.


        :param click_ch: (int|list of int) click channel number
                              positive/negative values - rising/falling edge detection
                              if list is given, clicks on all specified channels
                              will be merged into one logic channel

        :param gate_ch: (int) channel number
                             positive/negative - count during high/low gate level

        :return: (dict) actually channel assignment:
                        {
                            'click_channel': (int) click_chnl_num,
                            'gate_channel': (int) gate_chnl_num
                        }
        """

        if click_ch is not None:
            # for convenience bring int type of input to list of int
            if isinstance(click_ch, list):
                click_ch_list = click_ch
            elif isinstance(click_ch, int):
                click_ch_list = [click_ch]
            else:
                # unknown input type
                msg_str = 'set_ch_assignment(click_ch={0}): invalid argument type'\
                          ''.format(click_ch)
                self.log.error(msg_str=msg_str)
                raise CtrError(msg_str)

            # sanity check: all requested channels are available on the device
            all_chs = self.get_all_chs()
            for channel in click_ch_list:
                if channel not in all_chs:
                    msg_str = 'set_ch_assignment(): '\
                              'click_ch={0} - this channel is not available on the device'\
                              ''.format(click_ch)
                    self.log.error(msg_str=msg_str)
                    raise CtrError(msg_str)

            # If several channel numbers were passed, create virtual Combiner channel
            if len(click_ch_list) > 1:
                self._combiner = TT.Combiner(tagger=self._tagger,
                                             channels=click_ch_list)
                # Obtain int channel number for the virtual channel
                click_ch_list = [self._combiner.getChannel()]

            # Set new value for click channel
            self._click_ch = int(click_ch_list[0])

        if gate_ch is not None:

            # sanity check: channel is available on the device
            if gate_ch not in self.get_all_chs():
                msg_str = 'set_ch_assignment(): '\
                          'gate_ch={0} - this channel is not available on the device'\
                          ''.format(gate_ch)
                self.log.error(msg_str=msg_str)
                raise CtrError(msg_str)

            # Set new value for gate channel
            self._gate_ch = int(gate_ch)

        return self.get_ch_assignment()

    def get_all_chs(self):
        """Returns list of all channels available on the device,
        including edge type sign.

        Positive/negative numbers correspond to detection of rising/falling edges.
        For example:
            1 means 'rising edge on connector 1'
            -1 means 'falling edge on connector 1


        :return: (list of int) list of channel numbers including edge sign.
                Example: [-8, -7, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 6, 7, 8]
                Empty list is returned in the case of error.
        """

        # Sanity check: check that connection to the device was established
        if self._tagger is None:
            msg_str = 'get_all_chs(): not connected to the device yet'
            self.log.error(msg_str=msg_str)
            raise CtrError(msg_str)

        channel_list = list(
            self._tagger.getChannelList(
                TT.TT_CHANNEL_RISING_AND_FALLING_EDGES))
        return channel_list
Beispiel #21
0
class DataTaker:
    def __init__(self,
                 logger=None,
                 client_tuples=None,
                 config=None,
                 config_name=None):

        self.log = LogHandler(logger)
        self.dataset = None

        # Instantiate GUI window
        self.gui = Window(gui_template='data_taker', host=get_ip())

        # Configure list of experiments
        self.gui.config.setText(config_name)
        self.config = config
        self.exp_path = self.config['exp_path']
        if self.exp_path is None:
            self.exp_path = os.getcwd()
        sys.path.insert(1, self.exp_path)
        self.update_experiment_list()

        # Configure list of clients
        self.clients = {}

        # Configure list of missing clients
        self.missing_clients = {}

        # Setup Autosave
        # First check whether autosave is specified in config file
        if 'auto_save' in self.config:
            if self.config['auto_save']:
                self.gui.autosave.setChecked(True)

        # Retrieve Clients
        for client_entry in self.config['servers']:
            client_type = client_entry['type']
            client_config = client_entry['config']
            client = find_client(clients=client_tuples,
                                 settings=client_config,
                                 client_type=client_type,
                                 client_config=client_config,
                                 logger=self.log)
            if (client == None):
                self.missing_clients[f"{client_type}_{client_config}"] = [
                    client_type, client_config
                ]
            else:
                self.clients[f"{client_type}_{client_config}"] = client

        for client_name, client_obj in self.clients.items():
            client_item = QtWidgets.QListWidgetItem(client_name)
            client_item.setToolTip(str(client_obj))
            self.gui.clients.addItem(client_item)

        for client_name, client_config in self.missing_clients.items():
            client_item = QtWidgets.QListWidgetItem(client_name)
            client_item.setForeground(Qt.gray)
            self.gui.clients.addItem(client_item)
            self.log.error("Datataker missing client: " + client_name)

        # Configure button clicks
        self.gui.configure.clicked.connect(self.configure)
        self.gui.run.clicked.connect(self.run)
        self.gui.save.clicked.connect(self.save)
        self.gui.clearData.clicked.connect(self.clear_data)
        self.gui.load_config.clicked.connect(self.reload_config)
        self.gui.showMaximized()
        self.gui.apply_stylesheet()

    def update_experiment_list(self):
        """ Updates list of experiments """

        self.gui.exp.clear()
        for filename in os.listdir(self.exp_path):
            if filename.endswith('.py'):
                self.gui.exp.addItem(filename[:-3])
        self.gui.exp.itemClicked.connect(self.display_experiment)

    def display_experiment(self, item):
        """ Displays the currently clicked experiment in the text browser

        :param item: (QlistWidgetItem) with label of name of experiment to display
        """

        with open(os.path.join(self.exp_path, f'{item.text()}.py'),
                  'r') as exp_file:
            exp_content = exp_file.read()

        self.gui.exp_preview.setText(exp_content)
        self.gui.exp_preview.setStyleSheet('font: 10pt "Consolas"; '
                                           'color: rgb(255, 255, 255); '
                                           'background-color: rgb(0, 0, 0);')
        self.log.update_metadata(experiment_file=exp_content)

    def configure(self):
        """ Configures the currently selected experiment + dataset """

        # If the experiment is running, do nothing
        try:
            if self.experiment_thread.isRunning():
                self.log.warn('Did not configure experiment, since it '
                              'is still in progress')
                return
        except:
            pass

        # Load the config
        self.reload_config()

        # Set all experiments to normal state and highlight configured expt
        for item_no in range(self.gui.exp.count()):
            self.gui.exp.item(item_no).setBackground(
                QtGui.QBrush(QtGui.QColor('black')))
        self.gui.exp.currentItem().setBackground(
            QtGui.QBrush(QtGui.QColor('darkRed')))
        exp_name = self.gui.exp.currentItem().text()
        self.module = importlib.import_module(exp_name)
        self.module = importlib.reload(self.module)

        # Clear graph area and set up new or cleaned up dataset
        for index in reversed(range(self.gui.graph_layout.count())):
            try:
                self.gui.graph_layout.itemAt(index).widget().deleteLater()
            except AttributeError:
                try:
                    self.gui.graph_layout.itemAt(index).layout().deleteLater()
                except AttributeError:
                    pass
        self.gui.windows = {}
        # If we're not setting up a new measurement type, just clear the data

        # We are reading in the required base-dataset by looking at the define_dataset() as defined in the experiment script.
        try:
            classname = self.module.define_dataset()
        except AttributeError:
            error_msg = "No 'define_dataset' method found in experiment script."
            self.log.error(
                "No 'define_dataset' method found in experiment script.")
            return

        try:
            self.dataset = getattr(datasets, classname)(gui=self.gui,
                                                        log=self.log,
                                                        config=self.config)
        except AttributeError:
            error_msg = f"Dataset name {classname} as provided in 'define_dataset' method in experiment script is not valid."
            self.log.error(error_msg)
            return

        # Run any pre-experiment configuration
        try:
            self.module.configure(dataset=self.dataset, **self.clients)
        except AttributeError:
            pass
        self.experiment = self.module.experiment

        self.log.info(f'Experiment {exp_name} configured')
        self.gui.exp_preview.setStyleSheet(
            'font: 10pt "Consolas"; '
            'color: rgb(255, 255, 255); '
            'background-color: rgb(50, 50, 50);')

    def clear_data(self):
        """ Clears all data from curves"""
        self.log.info("Clearing data")
        self.dataset.clear_all_data()

    def run(self):
        """ Runs/stops the experiment """

        # Run experiment
        if self.gui.run.text() == 'Run':
            self.gui.run.setStyleSheet('background-color: red')
            self.gui.run.setText('Stop')
            self.log.info('Experiment started')

            # Run update thread
            self.update_thread = UpdateThread(
                autosave=self.gui.autosave.isChecked(),
                save_time=self.gui.autosave_interval.value())
            self.update_thread.data_updated.connect(self.dataset.update)
            self.update_thread.save_flag.connect(self.save)
            self.gui.autosave.toggled.connect(
                self.update_thread.update_autosave)
            self.gui.autosave_interval.valueChanged.connect(
                self.update_thread.update_autosave_interval)
            '''
            # Step 2: Create a QThread object
            self.thread = QThread()
            # Step 3: Create a worker object
            self.worker = Worker()
            # Step 4: Move worker to the thread
            self.worker.moveToThread(self.thread)
            # Step 5: Connect signals and slots
            self.thread.started.connect(self.worker.run)
            self.worker.finished.connect(self.thread.quit)
            self.worker.finished.connect(self.worker.deleteLater)
            self.thread.finished.connect(self.thread.deleteLater)
            self.worker.progress.connect(self.reportProgress)
            # Step 6: Start the thread
            self.thread.start()

            # Final resets
            self.longRunningBtn.setEnabled(False)
            self.thread.finished.connect(
                lambda: self.longRunningBtn.setEnabled(True)
            )
            self.thread.finished.connect(
                lambda: self.stepLabel.setText("Long-Running Step: 0")
            )
            '''

            self.experiment_thread = ExperimentThread(self.experiment,
                                                      dataset=self.dataset,
                                                      gui=self.gui,
                                                      **self.clients)

            self.experiment_thread.status_flag.connect(
                self.dataset.interpret_status)
            self.experiment_thread.finished.connect(self.stop)
            self.log.update_metadata(
                exp_start_time=datetime.now().strftime('%d/%m/%Y %H:%M:%S:%f'))

        # Stop experiment
        else:
            self.experiment_thread.running = False

    def stop(self):
        """ Stops the experiment"""

        self.gui.run.setStyleSheet('background-color: green')
        self.gui.run.setText('Run')
        self.log.info('Experiment stopped')
        self.update_thread.running = False

        self.log.update_metadata(
            exp_stop_time=datetime.now().strftime('%d/%m/%Y %H:%M:%S:%f'))

        # Autosave if relevant
        if self.gui.autosave.isChecked():
            self.save()

    def save(self):
        """ Saves data """

        self.log.update_metadata(notes=self.gui.notes.toPlainText())
        filename = self.gui.save_name.text()
        directory = self.config['save_path']
        self.dataset.save(filename=filename,
                          directory=directory,
                          date_dir=True)
        save_metadata(self.log, filename, directory, True)
        self.log.info('Data saved')

    def reload_config(self):
        """ Loads a new config file """

        self.config = load_script_config(script='data_taker',
                                         config=self.gui.config.text(),
                                         logger=self.log)
Beispiel #22
0
class Driver():
    """Driver class for GPIB controlled Agilent EE405 Spectrum analyser"""
    def reset(self):
        """ Create factory reset"""
        self.device.write('*RST')
        self.log.info("Reset to factory settings successfull.")

    def __init__(self, gpib_address, logger):
        """Instantiate driver class

        :gpib_address: GPIB-address of spectrum analyzer, e.g. 'GPIB0::12::INSTR'
            Can be read out by using
                rm = pyvisa.ResourceManager()
                rm.list_resources()
        :logger: And instance of a LogClient
        """

        # Instantiate log
        self.log = LogHandler(logger=logger)

        self.rm = ResourceManager()

        try:
            self.device = self.rm.open_resource(gpib_address)
            device_id = self.device.query('*IDN?')
            self.log.info(f"Successfully connected to {device_id}.")
        except VisaIOError:
            self.log.error(f"Connection to {gpib_address} failed.")

        # reset to factory settings
        self.reset()

    def display_off(self):
        """ Power off display """
        self.device.write(':DISPlay:ENABle OFF')
        self.log.info("Display off.")

    def display_on(self):
        """ Power on display """
        self.device.write(':DISPlay:ENABle ON')
        self.log.info("Display on.")

    def set_attenuation(self, db):
        """ Set input attenuation
        :db: Target attenuation in dB, must be between 0 and 75
        """

        if not 0 <= db <= 75:
            self.log.error(
                f'Invalid attenuation ({db}dB). Attenuation must be between 0dB and 75dB'
            )
        self.device.write(f'POW:ATT {int(db)}dB')
        self.log.info(f'Input attenuation set to {db}dB.')

    def set_reference_level(self, db):
        """Set reference level
        :db: Target reference level in dB
        """

        self.device.write(f':DISPlay:WINDow:TRACe:Y:RLEVel {int(db)}')
        self.log.info(f'Reference level set to {db}dB.')

    def set_center_frequency(self, center_frequency):
        """ Set center frequency of trace.

        :center_frequency: Frequency in Hz (from 0 to 13.3 GHz)
        """
        if not 0 <= center_frequency <= 13.2 * 1e9:
            self.log.error(
                f'Invalid center frequency ({center_frequency} Hz). Must be within 0 and 13.2 GHz'
            )

        self.device.write(f':SENSe:FREQuency:CENTer {center_frequency}')
        self.log.info(f'Center frequency set to {center_frequency} Hz')

    def set_frequency_span(self, frequency_span):
        """ Set frequency span of trace.

        :frequency_span: Frequency span in Hz (from 0 to 13.3 GHz)
        """
        if not 0 <= frequency_span <= 13.2 * 1e9:
            self.log.error(
                f'Invalid frequency span ({frequency_span} Hz). Must be within 0 and 13.2 GHz'
            )

        self.device.write(f':SENSe:FREQuency:SPAN {frequency_span}')
        self.log.info(f'Frequency span set {frequency_span} Hz')

    def toggle_cont(self, target_state):
        """Switch between single shot and continuous acquisition mode

        :target_state: Index of targe stat. 1 for continuous mode, 0 for single shot mode.
        """
        self.device.write(f'INIT:CONT {target_state}')

    def get_frequency_array(self):
        """Constructs array of frequencies associated with trace points"""

        # Sweep start frequency.
        start_freq = float(self.device.query(':SENSe:FREQuency:STARt?'))

        # Sweep end frequency.
        end_freq = float(self.device.query(':SENSe:FREQuency:STOP?'))

        # Number of sweep points.
        num_sweep_points = int(self.device.query('SENSE:SWEEP:POINTS?'))

        # Array containing frequencies of each sweep point.
        frequencies = np.linspace(start_freq, end_freq, num_sweep_points)

        return frequencies

    def read_trace(self):
        """ Read and return trace

        Retruns array trace contaning frequencies (in Hz) of data points in
        trace[:,0] and power levels (in dBm) in trace[:,1]
        """

        # Set to single shot mode.
        self.toggle_cont(0)

        # Trigger a sweep and wait for sweep to complete.
        self.device.write('INIT:IMM;*WAI')

        # Specify units in dBm.
        self.device.write('UNIT:POW DBM')

        # Specify data format as ASCII.
        self.device.write('FORM:DATA ASC')

        # Trigger a sweep and wait for sweep to complete.
        self.device.write('INIT:IMM;*WAI')

        # Query trace data
        dbm_measurement = self.device.query_ascii_values('TRAC:DATA? TRACE1')

        # Return to continuos monitoring mode
        self.toggle_cont(1)

        # Read frequency axis
        frequencies = self.get_frequency_array()

        # Combine trace data
        trace = np.stack((frequencies, dbm_measurement), axis=-1)

        return trace

    def acquire_background_spectrum(self, num_points=100):
        """Acquires an average background trace.

        :num_traces: How many traces to sample.
        :nocheck: Boolean indicating if user confirmation that no
            input signal is present will be omitted.
        """

        traces = []
        self.display_off()
        for i in range(num_points):
            traces.append(self.read_trace())
        self.display_on()

        noise_average = np.average(np.asarray(traces)[:, :, 1], 0)
        noise_trace = np.array([traces[0][:, 0], noise_average])

        self.noise_trace = np.transpose(noise_trace)

    def get_background(self):

        try:
            noise_trace = self.noise_trace
            return noise_trace
        except AttributeError:
            error_msg = 'No background acquired. Run acquire_background_spectrum() to acquire background.'
            self.log.warn(error_msg)
            return error_msg
Beispiel #23
0
class DLC_Pro:
    """ Driver class for Toptica DLC Pro """
    def __init__(self, host, port=1998, logger=None, num_lasers=2):
        """ Instantiates DLC_Pro object

        :param host: (str) hostname of laser (IP address)
        :param port: (int) port number, toptica defaults to 1998
        :num_lasers: Number of installed lasers
        :param logger: (LogClient)
        """

        self.host = host
        self.port = port
        self.log = LogHandler(logger)
        self.dlc = None
        self.laser_nums = range(1, num_lasers + 1)

        # Check connection
        try:

            # Check laser connection
            self.dlc = Telnet(host=self.host, port=self.port)
            self.dlc.read_until(b'>', timeout=1)

            for laser_num in self.laser_nums:
                self._check_laser_connection(laser_num)

        except ConnectionRefusedError:
            self.log.error('Could not connect to Toptica DLC Pro at '
                           f'IP address: {self.host}, port: {self.port}')

    def _check_laser_connection(self, laser_num=1):
        """ Read out laser number

        :laser_num: (int) 1 or 2, indicating laser 1 or laser 2.
        """
        self.dlc.read_until(b'>', timeout=1)
        self.dlc.write(
            f"(param-disp 'laser{laser_num}:dl:type)\n".encode('utf'))
        laser_type = self.dlc.read_until(
            b'>', timeout=1).split()[-3].decode('utf')[1:-1]
        self.dlc.write(
            f"(param-disp 'laser{laser_num}:dl:serial-number)\n".encode('utf'))
        serial = int(
            self.dlc.read_until(b'>',
                                timeout=1).split()[-3].decode('utf')[1:-1])
        self.log.info(
            f'Connected to Toptica {laser_type} {laser_num}, S/N {serial}')

    def is_laser_on(self, laser_num=1):
        """ Checks if the laser is on or off

        :return: (bool) whether or not emission is on or off
        """

        self.dlc.write(
            f"(param-disp 'laser{laser_num}:dl:cc:emission)\n".encode('utf'))
        result = self.dlc.read_until(b'>', timeout=1).split()[-3].decode('utf')
        status = result[1]
        if status == 't':
            return True
        elif status == 'f':
            return False
        else:
            self.log.warn(
                'Could not determine properly whether the laser is on or off')
            return False

    def turn_on(self, laser_num=1):
        """ Turns on the laser """

        # Check if laser is on already
        if self.is_laser_on(laser_num):
            self.log.info(f'Laser {laser_num} is already on')
        else:
            self.dlc.write(
                f"(param-set! 'laser{laser_num}:dl:cc:enabled #t)\n".encode(
                    'utf'))
            self.dlc.read_until(b'>', timeout=1)
            if self.is_laser_on(laser_num):
                self.log.info(f'Turned on Toptica DL-Pro laser {laser_num}')
            else:
                self.log.warn(
                    f'Laser {laser_num} could not be turned on. Physical emission button may need to be pressed'
                )

    def turn_off(self, laser_num=1):
        """ Turns off the laser """

        if self.is_laser_on(laser_num):
            self.dlc.write(
                f"(param-set! 'laser{laser_num}:dl:cc:enabled #f)\n".encode(
                    'utf'))
            self.dlc.read_until(b'>', timeout=1)
            if self.is_laser_on(laser_num):
                self.log.warn(
                    f'Failed to verify that DL-Pro laser {laser_num} turned off'
                )
            else:
                self.log.info(f'Turned off Toptica DL-Pro laser {laser_num}')
        else:
            self.log.info(f'Laser {laser_num} is already off')

    def voltage(self, laser_num=1):
        """ Gets current voltage on laser piezo

        :return: (float) current voltage on piezo
        """

        self.dlc.write(
            f"(param-disp 'laser{laser_num}:dl:pc:voltage-set)\n".encode(
                'utf'))
        voltage = float(self.dlc.read_until(b'>', timeout=1).split()[-3])

        return voltage

    def set_voltage(self, voltage, laser_num=1):
        """ Sets voltage to the piezo

        :param voltage: (float) voltage to set
        """

        v = deepcopy(voltage)
        write_data = f"(param-set! 'laser{laser_num}:dl:pc:voltage-set {v})\n".encode(
            'utf')
        self.dlc.write(write_data)
        self.dlc.read_until(b'>', timeout=1)

    def current_sp(self, laser_num=1):
        """ Gets current setpoint

        :return: (float) value of current setpoint
        """

        self.dlc.write(
            f"(param-disp 'laser{laser_num}:dl:cc:current-set)\n".encode(
                'utf'))
        return float(self.dlc.read_until(b'>', timeout=1).split()[-3])

    def current_act(self, laser_num=1):
        """ Gets measured current

        :return: (float) value of actual current
        """

        self.dlc.write(
            f"(param-disp 'laser{laser_num}:dl:cc:current-act)\n".encode(
                'utf'))
        return float(self.dlc.read_until(b'>', timeout=1).split()[-3])

    def set_current(self, current, laser_num=1):
        """ Sets the current to desired value

        :param current: (float) value of current to set as setpoint
        """

        c = deepcopy(current)
        write_data = f"(param-set! 'laser{laser_num}:dl:cc:current-set {c})\n".encode(
            'utf')
        self.dlc.write(write_data)
        self.dlc.read_until(b'>', timeout=1)

    def temp_sp(self, laser_num=1):
        """ Gets temperature setpoint

        :return: (float) value of temperature setpoint
        """

        self.dlc.write(
            f"(param-disp 'laser{laser_num}:dl:tc:temp-set)\n".encode('utf'))
        return float(self.dlc.read_until(b'>', timeout=1).split()[-3])

    def temp_act(self, laser_num=1):
        """ Gets actual DL temp

        :return: (float) value of temperature
        """

        self.dlc.write(
            f"(param-disp 'laser{laser_num}:dl:tc:temp-act)\n".encode('utf'))
        return float(self.dlc.read_until(b'>', timeout=1).split()[-3])

    def set_temp(self, temp, laser_num=1):
        """ Sets the current to desired value

        :param temp: (float) value of temperature to set to in Celsius
        """

        t = deepcopy(temp)
        write_data = f"(param-set! 'laser{laser_num}:dl:tc:temp-set {t})\n".encode(
            'utf')
        self.dlc.write(write_data)
        self.dlc.read_until(b'>', timeout=1)

    def configure_scan(self,
                       offset=65,
                       amplitude=100,
                       frequency=0.2,
                       laser_num=1):
        """ Sets the scan parameters for piezo scanning

        :param offset: (float) scan offset (center value) in volts (between 0 and 130)
        :param amplitude: (float) scan amplitude (peak to peak) in volts
        :param frequency: (Float) scan frequency in Hz
        """

        # Check that parameters are within range
        if (offset + (amplitude / 2) > 130) or (offset - (amplitude / 2) < -1):
            self.log.warn('Warning, invalid scan parameters set.'
                          'Make sure voltages are between -1 and 130 V')

        else:
            o = deepcopy(offset)
            write_data = f"(param-set! 'laser{laser_num}:scan:offset {o})\n".encode(
                'utf')
            self.dlc.write(write_data)
            self.dlc.read_until(b'>', timeout=1)

            a = deepcopy(amplitude)
            write_data = f"(param-set! 'laser{laser_num}:scan:amplitude {a})\n".encode(
                'utf')
            self.dlc.write(write_data)
            self.dlc.read_until(b'>', timeout=1)

            f = deepcopy(frequency)
            write_data = f"(param-set! 'laser{laser_num}:scan:frequency {f})\n".encode(
                'utf')
            self.dlc.write(write_data)
            self.dlc.read_until(b'>', timeout=1)

            self.log.info(
                f'Scan with offset {o}, amplitude {a}, frequency {f} for laser {laser_num} successfully configured.'
            )

    def start_scan(self, laser_num=1):
        """ Starts a piezo scan """

        write_data = f"(param-set! 'laser{laser_num}:scan:enabled #t)\n".encode(
            'utf')
        self.dlc.write(write_data)
        self.dlc.read_until(b'>', timeout=1)

    def stop_scan(self, laser_num=1):
        """ Stops piezo scan """

        write_data = f"(param-set! 'laser{laser_num}:scan:enabled #f)\n".encode(
            'utf')
        self.dlc.write(write_data)
        self.dlc.read_until(b'>', timeout=1)
Beispiel #24
0
class Wrap:
    def __init__(self, tagger, click_ch, start_ch, logger=None):
        """Instantiate gated counter

        :param tagger: instance of TimeTagger class
        :param click_ch: (int|list of int) clicks on all specified channels
                                    will be summed into one logical channel
        :param start_ch: (int) start trigger channel number
        """

        # Log
        self.log = LogHandler(logger=logger)

        # Reference to tagger
        self._tagger = tagger

        # Log device ID information to demonstrate that connection indeed works
        serial = self._tagger.getSerial()
        model = self._tagger.getModel()
        self.log.info(
            'Got reference to Swabian Instruments TimeTagger device \n'
            'Serial number: {0}, Model: {1}'
            ''.format(serial, model))

        # Gated Counter
        # reference to the TT.CountBetweenMarkers measurement instance
        self._ctr = None
        # number of count bins:
        #   length of returned 1D count array, the size of allocated memory buffer.
        # must be given as argument of init_ctr() call
        self._bin_n = 0

        # Channel assignments
        self._click_ch = 0
        self._start_ch = 0
        # reference to Combiner object
        #   (if _click_ch is a list - then counts on all channels are summed
        #   into virtual channel - self._combiner.getChannel())
        self._combiner = None
        # apply channel assignment
        self.set_ch_assignment(click_ch=click_ch, start_ch=start_ch)

    # ---------------- Interface ---------------------------

    def activate_interface(self):
        return 0

    def init_ctr(self, bin_n, bin_w):

        bin_w = int(bin_w / 1e-12)

        # Close existing counter, if it was initialized before
        self.close_ctr()

        # Instantiate counter measurement
        try:
            self._ctr = TT.TimeDifferences(tagger=self._tagger,
                                           click_channel=self._click_ch,
                                           start_channel=self._start_ch,
                                           n_bins=bin_n,
                                           binwidth=bin_w)

            # save bin_number in internal variable
            self._bin_n = bin_n

        # handle NotImplementedError (typical error, produced by TT functions)
        except NotImplementedError:
            # remove reference to the counter measurement
            self._ctr = None

            msg_str = 'init_ctr(): instantiation of TimeDifferences measurement failed'
            self.log.error(msg_str=msg_str)
            raise CtrError(msg_str)

        # Prepare counter to be started by start_counting()
        # (TimeDifferences measurement starts running immediately after instantiation,
        # so it is necessary to stop it and erase all counts collected between instantiation and stop() call)
        self._ctr.stop()
        self._ctr.clear()

        return 0

    def close_ctr(self):

        # Try to stop and to clear TT.CountBetweenMarkers measurement instance
        try:
            self._ctr.stop()
            self._ctr.clear()
        except:
            pass

        # Remove reference, set status to "void"
        self._ctr = None

        return 0

    def start_counting(self):

        # Try stopping and restarting counter measurement
        try:
            self._ctr.stop(
            )  # does not fail even if the measurement is not running
            self._ctr.clear()
            self._ctr.start()

            # Wait until the counter is actually ready to count
            time.sleep(0.1)

            return 0

        # handle exception in TT function calls [NotImplementedError]
        except NotImplementedError:
            # Since stop() and clear() methods are very robust,
            # this part is only executed if counter is totally broken.
            # In this case it makes sense to close counter.
            self.close_ctr()

            msg_str = 'start_counting(): call failed. Counter was closed. \n'\
                      'Re-initialize counter by calling init_ctr() again'
            self.log.error(msg_str=msg_str)
            raise CtrError(msg_str)

    def stop_counting(self):

        # Try stopping counter measurement
        try:
            # stop counter
            self._ctr.stop()

            return 0

        # handle exception in TT.stop()/TT.clear()
        except NotImplementedError:
            # Since stop() and clear() methods are very robust,
            # this part is only executed if counter is totally broken.
            # In this case it makes sense to close counter.
            self.close_ctr()

            msg_str = 'terminate_counting(): call failed. Counter was closed. \n' \
                      'Re-initialize it by calling init_ctr()'
            self.log.error(msg_str=msg_str)
            raise CtrError(msg_str)

    def get_count_trace(self):
        return self._ctr.getData()[0]

    # ------------------------------------------------------

    def get_ch_assignment(self):
        """Returns dictionary containing current channel assignment:
            {
                'click_ch': (int) click_channel_number_including_edge_sign
                'gate_ch': (int) gate_channel_number_including_edge_sign
            }

        :return: dict('click_ch': _, 'gate_ch': _)
        """

        click_ch = copy.deepcopy(self._click_ch)
        start_ch = copy.deepcopy(self._start_ch)

        return dict(click_ch=click_ch, start_ch=start_ch)

    def set_ch_assignment(self, click_ch=None, start_ch=None):
        """Sets click channel and and gate channel.

        This method only changes internal variables
        self._click_ch and self._gate_ch.
        To apply the channel update, call  init_ctr() again.


        :param click_ch: (int|list of int) click channel number
                              positive/negative values - rising/falling edge detection
                              if list is given, clicks on all specified channels
                              will be merged into one logic channel

        :param start_ch: (int) channel number
                             positive/negative - count during high/low gate level

        :return: (dict) actually channel assignment:
                        {
                            'click_channel': (int) click_chnl_num,
                            'gate_channel': (int) gate_chnl_num
                        }
        """

        if click_ch is not None:
            # for convenience bring int type of input to list of int
            if isinstance(click_ch, list):
                click_ch_list = click_ch
            elif isinstance(click_ch, int):
                click_ch_list = [click_ch]
            else:
                # unknown input type
                msg_str = 'set_ch_assignment(click_ch={0}): invalid argument type'\
                          ''.format(click_ch)
                self.log.error(msg_str=msg_str)
                raise CtrError(msg_str)

            # sanity check: all requested channels are available on the device
            all_chs = self.get_all_chs()
            for channel in click_ch_list:
                if channel not in all_chs:
                    msg_str = 'set_ch_assignment(): '\
                              'click_ch={0} - this channel is not available on the device'\
                              ''.format(click_ch)
                    self.log.error(msg_str=msg_str)
                    raise CtrError(msg_str)

            # If several channel numbers were passed, create virtual Combiner channel
            if len(click_ch_list) > 1:
                self._combiner = TT.Combiner(tagger=self._tagger,
                                             channels=click_ch_list)
                # Obtain int channel number for the virtual channel
                click_ch_list = [self._combiner.getChannel()]

            # Set new value for click channel
            self._click_ch = int(click_ch_list[0])

        if start_ch is not None:

            # sanity check: channel is available on the device
            if start_ch not in self.get_all_chs():
                msg_str = 'set_ch_assignment(): '\
                          'start_ch={0} - this channel is not available on the device'\
                          ''.format(start_ch)
                self.log.error(msg_str=msg_str)
                raise CtrError(msg_str)

            # Set new value for gate channel
            self._start_ch = int(start_ch)

        return self.get_ch_assignment()

    def get_all_chs(self):
        """Returns list of all channels available on the device,
        including edge type sign.

        Positive/negative numbers correspond to detection of rising/falling edges.
        For example:
            1 means 'rising edge on connector 1'
            -1 means 'falling edge on connector 1


        :return: (list of int) list of channel numbers including edge sign.
                Example: [-8, -7, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 6, 7, 8]
                Empty list is returned in the case of error.
        """

        # Sanity check: check that connection to the device was established
        if self._tagger is None:
            msg_str = 'get_all_chs(): not connected to the device yet'
            self.log.error(msg_str=msg_str)
            raise CtrError(msg_str)

        channel_list = list(
            self._tagger.getChannelList(
                TT.TT_CHANNEL_RISING_AND_FALLING_EDGES))
        return channel_list
Beispiel #25
0
class FW102CFilterWheel:
    def __init__(self, port_name, device_name, filters, logger=None):
        """Instantiate Hardware class for FW102c Filterwheel by instantiating a FW102C class

        :device_name: Readable name of device, e.g. 'Collection Filters'
        :port_name: Port name over which Filter wheel is connect via USB
        :filters: A dictionary where the keys are the numbered filter positions
                  and the values are strings describing the filter, e.g. '4 ND'
        """

        # Instantiate log
        self.log = LogHandler(logger=logger)

        # Retrieve name and filter options
        self.device_name = device_name
        self.filters = filters

        # Instantiate FW102C
        self.filter_wheel = FW102C(port=port_name, logger=self.log)

        if not self.filter_wheel.isOpen:
            self.log.error("Filterwheel {} connection failed".format(
                self.device_name))
        else:
            self.log.info("Filterwheel {} connection successfully".format(
                self.device_name))

    def get_name(self):
        return self.device_name

    def get_pos(self):
        """Returns current position of filterwheel
        """

        return self.filter_wheel.query('pos?')

    def change_filter(self, new_pos, protect_shutter_client=None):
        """ Update filter wheel position

        :new_pos: Target filter wheel position 1-6 or 1-12
        :protect_shutter_client: (optional) SC20Shutter instance.
            If provided, shutter will be closed during a filter change
        """

        # Close protection shutter if open
        if protect_shutter_client is not None:
            # Check if shutter is initially open
            shutter_open = protect_shutter_client.get_is_open()

            if shutter_open:
                protect_shutter_client.close()

        # Update Position
        self.filter_wheel.command('pos={}'.format(new_pos))

        successful_changed = False

        # Check if update was successful
        if int(self.get_pos()) == int(new_pos):
            self.log.info(
                "Filterwheel {device_name} changed to position {position} ({filter})"
                "".format(device_name=self.device_name,
                          position=new_pos,
                          filter=self.filters.get('{}'.format(new_pos))))
            successful_changed = False

            # Open protection shutter if shutter was originally open
            if protect_shutter_client is not None and shutter_open:
                protect_shutter_client.open()
        else:
            self.log.error(
                "Filterwheel {device_name}: changing to position failed"
                "".format(device_name=self.device_name))

        return successful_changed

    # returns filter dictionary

    def get_filter_dict(self):
        return self.filters
Beispiel #26
0
class Sweep1D:

    def __init__(self, logger=None, sweep_type='triangle'):
        """ Instantiates sweeper

        :param logger: instance of LogClient
        """

        self.log = LogHandler(logger)

        self.min = 0
        self.max = 1
        self.pts = 51
        self.experiment = None
        self.fixed_params = {}
        self.iplot_fwd = None
        self.hplot_fwd = None
        self.iplot_bwd = None
        self.hplot_bwd = None
        self.sweep_type = sweep_type
        self.reps = 0
        self.stop_flag = False
        self.stop_end_flag = False
        self.x_label = None
        self.y_label = None
        self.autosave = False

        # Setup stylesheet.
        #self.gui.apply_stylesheet()


    def set_parameters(self, **kwargs):
        """ Configures all parameters

        :param kwargs: (dict) containing parameters
            :min: (float) minimum value to sweep from
            :max: (float) maximum value to sweep to
            :pts: (int) number of points to use
            :reps: (int) number of experiment repetitions
            :sweep_type: (str) 'triangle' or 'sawtooth' supported
            :x_label: (str) Label of x axis
            :y_label: (str) label of y axis
        """

        if 'min' in kwargs:
            self.min = kwargs['min']
        if 'max' in kwargs:
            self.max = kwargs['max']
        if 'pts' in kwargs:
            self.pts = kwargs['pts']
        if 'sweep_type' in kwargs:
            sweep_str = kwargs['sweep_type']
            if sweep_str not in ['sawtooth',  'triangle']:
                self.log.error(
                    'Sweep type must be either "sawtooth" or "triangle".'
                )
            self.sweep_type = sweep_str
        if 'reps' in kwargs:
            self.reps = kwargs['reps']
        if 'x_label' in kwargs:
            self.x_label = kwargs['x_label']
        if 'y_label' in kwargs:
            self.y_label = kwargs['y_label']

    def configure_experiment(
        self, experiment, experiment_params={}
    ):
        """ Sets the experimental script to a provided module

        :param experiment: (callable) method to run
        :param experiment_params: (dict) containing name and value of
            fixed parameters
        """

        self.experiment = experiment
        self.fixed_params = experiment_params

    def run_once(self, param_value):
        """ Runs the experiment once for a parameter value

        :param_value: (float) value of parameter to use
        :return: (float) value resulting from experiment call
        """

        result = self.experiment(
            param_value,
            **self.fixed_params
        )

        if not result:
            self.widgets['rep_tracker'].setValue(0)
            self.widgets['reps'].setValue(0)
            for button in self.widgets['avg']:
                button.setText('Avg only')
            self.widgets['run'].setStyleSheet('background-color: green')
            self.widgets['run'].setText('Run')
            self.stop()
            self.log.info('Sweep experiment auto-aborted')

        return result

    def run(self, plot=False, autosave=None, filename=None, directory=None, date_dir=True):
        """ Runs the sweeper

        :param plot: (bool) whether or not to display the plotly plot
        :param autosave: (bool) whether or not to autosave
        :param filename: (str) name of file identifier
        :param directory: (str) filepath to save to
        :param date_dir: (bool) whether or not to store in date-specific sub-directory
        """

        if autosave is not None:
            self.autosave = autosave

        sweep_points = self._generate_x_axis()
        if self.sweep_type != 'sawtooth':
            bw_sweep_points = self._generate_x_axis(backward=True)
        self._configure_plots(plot)

        reps_done = 0
        self.stop_flag = False
        while (reps_done < self.reps or self.reps <= 0 and not self.stop_flag):

            self._reset_plots()

            for x_value in sweep_points:
                if self.stop_flag:
                    break
                self._run_and_plot(x_value)

            if self.sweep_type != 'sawtooth':
                for x_value in bw_sweep_points:
                    if self.stop_flag:
                        break
                    self._run_and_plot(x_value, backward=True)

            if self.stop_flag:
                break
            reps_done += 1
            self._update_hmaps(reps_done)
            self._update_integrated(reps_done)

            # Autosave at every iteration
            if self.autosave:
                self.save(filename, directory, date_dir)

            # Print progress
            print(f'Finished {reps_done} out of {self.reps} sweeps.')

    def stop(self):
        """ Terminates the sweeper immediately """

        self.stop_flag = True

    def set_reps(self, reps=1):
        """ Sets the repetitions to be done

        :param reps: (int) number of refs
            default = 1, can be used to terminate at end of current rep
        """
        self.reps = reps

    def save(self, filename=None, directory=None, date_dir=True):
        """ Saves the dataset

        :param filename: (str) name of file identifier
        :param directory: (str) filepath to save to
        :param date_dir: (bool) whether or not to store in date-specific sub-directory
        """

        if filename is None:
            filename = 'sweeper_data'

        # Save heatmap
        generic_save(
            data=self.hplot_fwd._fig.data[0].z,
            filename=f'{filename}_fwd_scans',
            directory=directory,
            date_dir=date_dir
        )

        # Save heatmap png
        # plotly_figure_save(
        #     self.hplot_fwd._fig,
        #     filename=f'{filename}_fwd_scans',
        #     directory=directory,
        #     date_dir=date_dir
        # )

        # Save average
        generic_save(
            data = np.array(
                    [self.iplot_fwd._fig.data[1].x,
                    self.iplot_fwd._fig.data[1].y]
            ),
            filename=f'{filename}_fwd_avg',
            directory=directory,
            date_dir=date_dir
        )

        if self.sweep_type != 'sawtooth':

            # Save heatmap
            generic_save(
                data=self.hplot_bwd._fig.data[0].z,
                filename=f'{filename}_bwd_scans',
                directory=directory,
                date_dir=date_dir
            )
            # Save average
            generic_save(
                data = np.array(
                        [self.iplot_fwd._fig.data[1].x,
                        self.iplot_fwd._fig.data[1].y]
                ),
                filename=f'{filename}_bwd_avg',
                directory=directory,
                date_dir=date_dir
            )

    def _generate_x_axis(self, backward=False):
        """ Generates an x-axis based on the type of sweep

        Currently only implements triangle
        :param backward: (bool) whether or not it is a backward scan
        :return: (np.array) containing points to scan over
        """

        if backward:
            return np.linspace(self.max, self.min, self.pts)
        else:
            return np.linspace(self.min, self.max, self.pts)

    def _configure_plots(self, plot):
        """ Configures all plots

        :param plot: (bool) whether or not to display the plotly plot
        """

        # single-trace scans
        self.iplot_fwd = MultiTraceFig(title_str='Forward Scan', ch_names=['Single', 'Average'])
        self.iplot_fwd.set_data(x_ar=np.array([]), y_ar=np.array([]), ind=0)
        self.iplot_fwd.set_data(x_ar=np.array([]), y_ar=np.array([]), ind=1)
        self.iplot_fwd.set_lbls(x_str=self.x_label, y_str=self.y_label)

        # heat map
        self.hplot_fwd = HeatMapFig(title_str='Forward Scans')
        self.hplot_fwd.set_data(
            x_ar=np.linspace(self.min, self.max, self.pts),
            y_ar=np.array([]),
            z_ar=np.array([[]])
        )
        self.hplot_fwd.set_lbls(
            x_str=self.x_label,
            y_str='Repetition number',
            z_str=self.y_label
        )

        # Show plots if enabled
        if plot:
            self.iplot_fwd.show()
            self.hplot_fwd.show()

        if self.sweep_type != 'sawtooth':
            self.iplot_bwd = MultiTraceFig(title_str='Backward Scan', ch_names=['Single', 'Average'])
            self.iplot_bwd.set_data(x_ar=np.array([]), y_ar=np.array([]), ind=0)
            self.iplot_bwd.set_data(x_ar=np.array([]), y_ar=np.array([]), ind=1)
            self.iplot_bwd.set_lbls(x_str=self.x_label, y_str=self.y_label)

            # heat map
            self.hplot_bwd = HeatMapFig(title_str='Backward Scans')
            self.hplot_bwd.set_data(
                x_ar=np.linspace(self.max, self.min, self.pts),
                y_ar=np.array([]),
                z_ar=np.array([[]])
            )
            self.hplot_bwd.set_lbls(
                x_str=self.x_label,
                y_str='Repetition number',
                z_str=self.y_label
            )

            # Show plots if enabled
            self.iplot_bwd.show()
            self.hplot_bwd.show()


    def _run_and_plot(self, x_value, backward=False):
        """ Runs the experiment for an x value and adds to plot

        :param x_value: (double) experiment parameter
        :param backward: (bool) whether or not backward or forward
        """

        y_value = self.run_once(x_value)
        if backward:
            self.iplot_bwd.append_data(x_ar=x_value, y_ar=y_value, ind=0)
        else:
            self.iplot_fwd.append_data(x_ar=x_value, y_ar=y_value, ind=0)

    def _update_hmaps(self, reps_done):
        """ Updates heat map plots

        :param reps_done: (int) number of repetitions done
        """

        if reps_done == 1:
            self.hplot_fwd.set_data(
                y_ar=np.array([1]),
                z_ar=[self.iplot_fwd._y_ar]
            )
            if self.sweep_type != 'sawtooth':
                self.hplot_bwd.set_data(
                    y_ar=np.array([1]),
                    z_ar=[self.iplot_bwd._y_ar]
                )
        else:
            self.hplot_fwd.append_row(y_val=reps_done, z_ar=self.iplot_fwd._fig.data[0].y)
            if self.sweep_type != 'sawtooth':
                self.hplot_bwd.append_row(y_val=reps_done, z_ar=self.iplot_bwd._fig.data[0].y)

    def _reset_plots(self):
        """ Resets single scan traces """

        self.iplot_fwd.set_data(x_ar=np.array([]), y_ar=np.array([]))
        if self.sweep_type != 'sawtooth':
            self.iplot_bwd.set_data(x_ar=np.array([]), y_ar=np.array([]))

    def _update_integrated(self, reps_done):
        """ Updates integrated plots

        :param reps_done: (int) number of repetitions completed
        """

        if reps_done==1:
            self.iplot_fwd.set_data(
                x_ar=np.linspace(self.min, self.max, self.pts),
                y_ar=self.iplot_fwd._fig.data[0].y,
                ind=1
            )
            if self.sweep_type != 'sawtooth':
                self.iplot_bwd.set_data(
                    x_ar=np.linspace(self.max, self.min, self.pts),
                    y_ar=self.iplot_bwd._fig.data[0].y,
                    ind=1
                )

        else:
            self.iplot_fwd.set_data(
                x_ar=np.linspace(self.min, self.max, self.pts),
                y_ar=((self.iplot_fwd._fig.data[1].y*(reps_done-1)/reps_done)
                      +self.iplot_fwd._fig.data[0].y/reps_done),
                ind=1
            )

            if self.sweep_type != 'sawtooth':
                self.iplot_bwd.set_data(
                    x_ar=np.linspace(self.max, self.min, self.pts),
                    y_ar=((self.iplot_bwd._fig.data[1].y*(reps_done-1)/reps_done)
                          +self.iplot_bwd._fig.data[0].y/reps_done),
                    ind=1
                )
class Driver():

    def reset(self):
        """ Create factory reset"""
        self.device.write('FAC;WAIT')
        self.log.info("Reset to factory settings successfull.")

    def __init__(self, gpib_address, logger):
        """Instantiate driver class.

        :gpib_address: GPIB-address of the scope, e.g. 'GPIB0::12::INSTR'
            Can be read out by using
                rm = pyvisa.ResourceManager()
                rm.list_resources()
        :logger: And instance of a LogClient.
        """

        # Instantiate log.
        self.log = LogHandler(logger=logger)

        self.rm = ResourceManager()

        try:
            self.device = self.rm.open_resource(gpib_address)
            device_id = self.device.query('*IDN?')
            self.log.info(f"Successfully connected to {device_id}.")
        except VisaIOError:
            self.log.error(f"Connection to {gpib_address} failed.")

        # We set a more forgiving timeout of 10s (default: 2s).
        self.device.timeout = 10000

        # Reset to factory settings.
        self.reset()

        # Set all attenuations to 1x.
        for channel in CHANNEL_LIST:
            self.set_channel_attenuation(channel, 1)

    def get_trigger_source(self):
        """ Return Trigger source."""

        # Query trigger source.
        res = self.device.query('TRIG:MAI:EDGE:SOU?')

        # Tidy up response using regex.
        trig_channel = re.compile(
            ':TRIGGER:MAIN:EDGE:SOURCE[ ]([^\\n]+)'
        ).match(res).group(1)

        return trig_channel

    def set_trigger_source(self, trigger_source):
        """ Set trigger source."""

        if trigger_source not in TRIGGER_SOURCE_LIST:
            self.log.error(
                f"'{trigger_source}' no found, available trigger sources are {TRIGGER_SOURCE_LIST}.'"
            )

        # Set trigger source.
        self.device.write(f'TRIG:MAI:EDGE:SOU {trigger_source}')

    def set_timing_scale(self, scale):
        """ Set the time base.

        This defines the available display window, as 10
        divisions are displayed.

        :scale: Time per division (in s)
        """
        self.device.write(":HORIZONTAL:MAIN:SCALE {:e}".format(scale))

    def extract_params(self, command, value):
        """ Uses regex to extract float values from return values.

        :command: The command used to query, without the final '?'
        :value: The return value of a query.
        """

        value = float(re.compile(
            f'{command}[ ]([0-9\.\+Ee-]+)'
        ).match(value).group(1))

        return value

    def get_timing_scale(self):
        """ Get time base in secs per division."""

        command = ":HORIZONTAL:MAIN:SCALE"
        timing_res = self.device.query(f"{command}?")

        timing_res = self.extract_params(command, timing_res)

        return timing_res

    def set_single_run_acq(self):
        """Set acquisition mode to single run."""

        self.device.write('acquire:stopafter sequence')

    def acquire_single_run(self):
        """ Run single acquisition."""

        self.device.write('acquire:state on')

    def _check_channel(self, channel):
        """ CHeck if channel is in CHANNEL list."""

        if channel not in CHANNEL_LIST:
            self.log.error(
                f"The channel '{channel}' is not available, available channels are {CHANNEL_LIST}."
            )

    def unitize_trace(self, trace, trace_preamble):
        """Transform unitless trace to trace with units, constructs time array.

        :trace: (np.array) Unitless array as provided by oscilloscope.
        :trace_preamble: (string) Waveform preamble.

        Returns trace, a np.array in correct units, ts, the time
        array in seconds, and y_unit, the unit of the Y-axis.
        """

        # Overcharged regex extracting all relevant paramters.
        wave_pre_regex = 'NR_PT (?P<n_points>[0-9\.\+Ee-]+).+XINCR (?P<x_incr>[0-9\.\+Ee-]+).+PT_OFF (?P<pt_off>[0-9\.\+Ee-]+).+XZERO (?P<x_zero>[0-9\.\+Ee-]+).+XUNIT "(?P<x_unit>[^"]+).+YMULT (?P<y_mult>[0-9\.\+Ee-]+).+YZERO (?P<y_zero>[0-9\.\+Ee-]+).+YOFF (?P<y_off>[0-9\.\+Ee-]+).+YUNIT "(?P<y_unit>[^"]+)'

        wave_pre_matches = re.search(wave_pre_regex, trace_preamble)

        # Adjust trace as shown in the coding manual 2-255.
        trace = (
            trace - float(wave_pre_matches['y_off'])
        ) * \
            float(wave_pre_matches['y_mult']) + \
            float(wave_pre_matches['y_zero'])

        # Construct timing array as shown in the coding manual 2-250.
        ts = float(wave_pre_matches['x_zero']) + \
            (
                np.arange(int(wave_pre_matches['n_points'])) -
                int(wave_pre_matches['pt_off'])
        ) * float(wave_pre_matches['x_incr'])

        x_unit = wave_pre_matches['x_unit']
        y_unit = wave_pre_matches['y_unit']

        # Construct trace dictionary.
        trace_dict = {
            'trace': trace,
            'ts': ts,
            'x_unit': x_unit,
            'y_unit': y_unit
        }

        return trace_dict

    def read_out_trace(self, channel, curve_res=1):
        """ Read out trace

        :channel: Channel to read out (must be in CHANNEL_LIST).
        :curve_res: Bit resolution for returned data. If 1, value range is from -127 to 127,
            if 2, the value range is from -32768 to 32768.

        Returns np.array of sample points (in unit of Voltage divisions) and
        corresponding array of times (in seconds).
        """

        self._check_channel(channel)

        # Enable trace.
        self.show_trace(channel)

        # Run acquisition.
        self.acquire_single_run()

        if curve_res not in [1, 2]:
            self.log.error("The bit resolution of the curve data must be either 1 or 2.")

        # Set curve data to desired bit.
        self.device.write(f'DATa:WIDth {curve_res}')

        # Set trace we want to look at.
        self.device.write(f'DATa:SOUrce {channel}')

        # Set encoding.
        self.device.write('data:encdg ascii')

        # Read out trace.
        res = self.device.query('curve?')

        # Tidy up curve.
        raw_curve = res.replace(':CURVE', '').replace(' ', '').replace('\n', '')

        # Transform in numpy array.
        trace = np.fromstring(raw_curve, dtype=int, sep=',')

        # Read wave preamble.
        wave_pre = self.device.query('WFMPre?')

        # Transform units of trace.
        trace_dict = self.unitize_trace(trace, wave_pre)

        return trace_dict

    def show_trace(self, channel):
        """Display trace.

        Required for trace readout.
        """

        self._check_channel(channel)

        self.device.write(f'SELect:{channel} 1')

    def hide_trace(self, channel):
        """Hide trace."""

        self._check_channel(channel)

        self.device.write(f'SELect:{channel} 0')

    def _check_channel_attenuation(self, attenuation):
        """Check if attenuation is within option set."""

        if attenuation not in ATTENUATIONS:
            self.log.error(
                f"The attenuation '{attenuation}x' is not available, available attenuations are {ATTENUATIONS}."
            )

    def get_channel_attenuation(self, channel):
        """Get the attenuation of the channel.

        :channel: (str) Channel, possible values see CHANNEL_LIST.
        """

        # Check if channel and attenuation is valid.
        self._check_channel(channel)

        # Get attenuation.
        command = f":{channel}:PROBE"
        attenuation = self.device.query(f"{command}?")

        # Extract float.
        attenuation = self.extract_params(command, attenuation)

        return attenuation

    def set_channel_attenuation(self, channel, attenuation):
        """Set the attenuation of the channel.

        This setting will scale the y-axis unit accordingly

        :channel: (str) Channel, possible values see CHANNEL_LIST.
        :attenuation: (int) Attenuation, possible values see ATTENUATIONS.
        """

        # Check if channel and attenuation is valid.
        self._check_channel(channel)
        self._check_channel_attenuation(attenuation)

        # Set attenuation.
        self.device.write(f'{channel}:PRObe {attenuation}')

    def get_channel_scale(self, channel):
        """ Return vertical scale of channel.

        :channel: (str) Channel, possible values see CHANNEL_LIST.
        """

        self._check_channel(channel)
        command = f":{channel}:SCALE"
        scale = self.device.query(f"{command}?")

        # Extract float.
        scale = self.extract_params(command, scale)

        return scale

    def set_channel_scale(self, channel, range):
        """ Return vertical scale of channel.

        :channel: (str) Channel, possible values see CHANNEL_LIST.
        :range: (float) Vertical range, in Volt/vertical division.
            Corresponds to 'Scale' turning knob.
            Must be between 5 mv/div and 5 V/div.
        """

        self._check_channel(channel)

        if not (5e-3 <= range <= 5):
            self.log.error('Range must be between 5 mv/div and 5 V/div.')

        # Set scale.
        self.device.write(f'{channel}:SCAle {range}')

    def get_channel_pos(self, channel):
        """Get vertical position of channel trace.

        :channel: (str) Channel, possible values see CHANNEL_LIST.
        """

        self._check_channel(channel)

        command = f":{channel}:POSITION"
        pos = self.device.query(f"{command}?")

        # Extract float.
        pos = self.extract_params(command, pos)

        return pos

    def set_channel_pos(self, channel, pos):
        """Set vertical position of channel trace.

        :channel: (str) Channel, possible values see CHANNEL_LIST.
        :pos: (str) Vertical postion, in divs above center graticule.
            The maximum and minimum value of pos
            depends on the channel scale.
        """

        self._check_channel(channel)

        self.device.write(f'{channel}:POS {pos}')

    def get_horizontal_position(self):
        """Get the horizontal position of the traces.

        The return value in seconds is the difference between
        the trigger point ant the center graticule.
        """

        command = ":HORIZONTAL:MAIN:POSITION"
        hor_pos = self.device.query(f"{command}?")

        hor_pos = self.extract_params(command, hor_pos)

        return hor_pos

    def set_horizontal_position(self, hor_pos):
        """Set the horizontal position of the traces.

        The return value in seconds is the difference between
        the trigger point ant the center graticule.

        :hor_pos: (float) Horizontal position in s.
        """

        command = ":HORIZONTAL:MAIN:POSITION"
        self.device.write(f"{command} {hor_pos}")

    def trig_level_to_fifty(self):
        """Set main trigger level to 50%"""
        self.device.write('TRIGger:MAIn SETLEVel')

    def get_trigger_level(self):
        """Get trigger level."""

        trig_level = self.device.query(':TRIGGER:MAIN:LEVEL?')
        trig_level = self.extract_params(':TRIGGER:MAIN:LEVEL', trig_level)

        return trig_level

    def set_trigger_level(self, trigger_level):
        """Set trigger level.

        :trigger_level: (float) Trigger level in Volts.
        """

        self.device.write(f':TRIGGER:MAIN:LEVEL {trigger_level}')
Beispiel #28
0
class Driver():
    def __init__(self, device_num, logger):
        """Instantiate driver class.

        device_num is numbering of devices connected via USB. Drivrt then finds serial numbers of polarization paddle by Driver, e.g. b'38154354' """

        # Instantiate log.
        self.log = LogHandler(logger=logger)
        #Loads polarization contorolles DLL and define arguments and result 5types for c function
        self._polarizationdll = ctypes.cdll.LoadLibrary(
            'Thorlabs.MotionControl.Polarizer.dll')
        self._devmanagerdll = ctypes.cdll.LoadLibrary(
            'Thorlabs.MotionControl.DeviceManager.dll')
        self._configure_functions()

        #get device list size
        if self._polarizationdll.TLI_BuildDeviceList() == 0:
            num_devs = self._polarizationdll.TLI_GetDeviceListSize()
            #print(f"There are {num_devs} devices connected")

        #Get devices serial numbers
        serialNos = ctypes.create_string_buffer(
            100)  #the way to have a mutable buffer
        serialNosSize = ctypes.c_ulong(ctypes.sizeof(serialNos))
        List = self._polarizationdll.TLI_GetDeviceListByTypeExt(
            serialNos, serialNosSize, 38)

        #if List:
        #    print("Failed to get device list")
        #else:
        #    print("Device list created succesfully") #change these massages to interact with logger

        self.dev_name = serialNos.value.decode("utf-8")  #.strip().split(',')
        #print(f"Connected to device {self.dev_name}")

        #get device info including serial number
        self.device_info = TLI_DeviceInfo()  # container for device info
        self._polarizationdll.TLI_GetDeviceInfo(
            serialNos[(device_num - 1) * 9:(device_num * 9) - 1],
            ctypes.byref(self.device_info)
        )  #when there will be a few devices figure out how to seperate and access each one
        self.device = serialNos[(device_num - 1) * 9:(device_num * 9) - 1]

        #print("Description: ", self.device_info.description)
        #print("Serial No: ", self.device_info.serialNo)
        #print("Motor Type: ", self.device_info.motorType)
        #print("USB PID: ", self.device_info.PID)
        #print("Max Number of Paddles: ", self.device_info.maxPaddles)

        #establising connection to device
        self.paddles = [paddle1, paddle3, paddle2]

        connection = self._polarizationdll.MPC_Open(self.device)
        if connection == 0:
            self.log.info(f"Successfully connected to {self.device}.")
        else:
            self.log.error(
                f"Connection to {self.device} failed due to error {connection}."
            )

    #technical methods

    def _configure_functions(self):
        """ Defines arguments and results for c functions """

        self._polarizationdll.TLI_BuildDeviceList.argtype = None
        self._polarizationdll.TLI_BuildDeviceList.restype = ctypes.c_short
        self._polarizationdll.TLI_GetDeviceListSize.argtype = None
        self._polarizationdll.TLI_GetDeviceListSize.restype = ctypes.c_short
        self._polarizationdll.TLI_GetDeviceInfo.argtypes = [
            ctypes.POINTER(ctypes.c_char),
            ctypes.POINTER(TLI_DeviceInfo)
        ]
        self._polarizationdll.TLI_GetDeviceInfo.restype = ctypes.c_short
        self._polarizationdll.TLI_GetDeviceListByTypeExt.argtypes = [
            ctypes.POINTER(ctypes.c_char), ctypes.c_ulong, ctypes.c_int
        ]
        self._polarizationdll.TLI_GetDeviceListByTypeExt.restype = ctypes.c_short
        self._polarizationdll.MPC_Open.argtype = ctypes.POINTER(ctypes.c_char)
        self._polarizationdll.MPC_Open.restype = ctypes.c_short
        self._polarizationdll.MPC_Close.argtype = ctypes.POINTER(ctypes.c_char)
        self._polarizationdll.MPC_Close.restype = ctypes.c_short
        self._polarizationdll.MPC_CheckConnection.argtype = ctypes.c_char_p
        self._polarizationdll.MPC_CheckConnection.restype = ctypes.c_bool
        self._polarizationdll.MPC_GetPosition.argtypes = [
            ctypes.POINTER(ctypes.c_char), POL_Paddles
        ]
        self._polarizationdll.MPC_GetPosition.restype = ctypes.c_double
        self._polarizationdll.MPC_RequestPolParams.argtype = ctypes.POINTER(
            ctypes.c_char)
        self._polarizationdll.MPC_RequestPolParams.restype = ctypes.c_short
        self._polarizationdll.MPC_GetPolParams.argtypes = [
            ctypes.POINTER(ctypes.c_char),
            ctypes.POINTER(TLI_PolarizerParameters)
        ]
        self._polarizationdll.MPC_GetPolParams.restype = ctypes.c_short
        self._polarizationdll.MPC_SetPolParams.argtypes = [
            ctypes.POINTER(ctypes.c_char),
            ctypes.POINTER(TLI_PolarizerParameters)
        ]
        self._polarizationdll.MPC_SetPolParams.restype = ctypes.c_short
        self._polarizationdll.MPC_SetJogSize.argtypes = [
            ctypes.POINTER(ctypes.c_char), POL_Paddles, ctypes.c_double
        ]
        self._polarizationdll.MPC_SetJogSize.restype = ctypes.c_short
        self._polarizationdll.MPC_Jog.argtypes = [
            ctypes.POINTER(ctypes.c_char), POL_Paddles, MOT_TravelDirection
        ]
        self._polarizationdll.MPC_Jog.restype = ctypes.c_short
        self._polarizationdll.MPC_GetMaxTravel.argtype = ctypes.POINTER(
            ctypes.c_char)
        self._polarizationdll.MPC_GetMaxTravel.restype = ctypes.c_double
        self._polarizationdll.MPC_MoveToPosition.argtypes = [
            ctypes.POINTER(ctypes.c_char), POL_Paddles, ctypes.c_double
        ]
        self._polarizationdll.MPC_MoveToPosition.restype = ctypes.c_short
        self._polarizationdll.MPC_Stop.argtypes = [
            ctypes.POINTER(ctypes.c_char), POL_Paddles
        ]
        self._polarizationdll.MPC_Stop.restype = ctypes.c_short
        self._polarizationdll.MPC_Home.argtypes = [
            ctypes.POINTER(ctypes.c_char), POL_Paddles
        ]
        self._polarizationdll.MPC_Home.restype = ctypes.c_short
        self._polarizationdll.MPC_Jog.argtypes = [
            ctypes.POINTER(ctypes.c_char),
            ctypes.POINTER(TLI_PolarizerParameters), MOT_TravelDirection
        ]
        self._polarizationdll.MPC_Jog.restype = ctypes.c_short
        self._polarizationdll.MPC_StartPolling.argtypes = [
            ctypes.POINTER(ctypes.c_char), ctypes.c_int
        ]
        self._polarizationdll.MPC_StartPolling.restype = ctypes.c_bool
        self._polarizationdll.MPC_StopPolling.argtype = ctypes.POINTER(
            ctypes.c_char)
        self._polarizationdll.MPC_StopPolling.restype = ctypes.c_void_p  #did not find the a c_void with no pointer as needed
        self._polarizationdll.MPC_SetVelocity.argtypes = [
            ctypes.POINTER(ctypes.c_char), ctypes.c_short
        ]
        self._polarizationdll.MPC_SetVelocity.restype = ctypes.c_short
        self._polarizationdll.MPC_MoveRelative.argtypes = [
            ctypes.POINTER(ctypes.c_char), POL_Paddles, ctypes.c_double
        ]
        self._polarizationdll.MPC_MoveRelative.restype = ctypes.c_short
        self._polarizationdll.MPC_GetStepsPerDegree.argtype = [
            ctypes.POINTER(ctypes.c_char)
        ]
        self._polarizationdll.MPC_GetStepsPerDegree.result = ctypes.c_double

        #wrap function for external use

    def open(self):
        result = self._polarizationdll.MPC_Open(self.device)
        if result == 0:
            print("Connected succesfully to device")
        else:
            print("A problem occured when trying to connect to device")

    def close(self):
        resultc = self._polarizationdll.MPC_Close(self.device)
        if resultc == 0:
            print("Closed connection to device")
        else:
            print("A problem occured when trying to diconnect from device")

    def home(self, paddle_num):
        home_result = self._polarizationdll.MPC_Home(self.device,
                                                     self.paddles[paddle_num])

        return home_result

    def set_velocity(self, velocity):
        velocity = self._polarizationdll.MPC_SetVelocity(self.device, velocity)

    def move(self, paddle_num, pos, sleep_time):
        #posinitial = self._polarizationdll.MPC_GetPosition(self.device,  self.paddles[paddle_num])
        move_result = self._polarizationdll.MPC_MoveToPosition(
            self.device, self.paddles[paddle_num], pos)
        time.sleep(abs(sleep_time * pos / 170))
        #posfinal = self._polarizationdll.MPC_GetPosition(self.device, self.paddles[paddle_num])

        return move_result  #, posinitial, posfinal

    def move_rel(self, paddle_num, step, sleep_time):
        #posinitial = self._polarizationdll.MPC_GetPosition(self.device, self.paddles[paddle_num])
        move_result = self._polarizationdll.MPC_MoveRelative(
            self.device, self.paddles[paddle_num], step)
        time.sleep(abs(sleep_time * step / 170))
        #posfinal = self._polarizationdll.MPC_GetPosition(self.device, self.paddles[paddle_num])

        return move_result  #, posinitial, posfinal

    def get_angle(self, paddle_num):
        currentpos = self._polarizationdll.MPC_GetPosition(
            self.device, self.paddles[paddle_num])

        return currentpos
Beispiel #29
0
class Driver:
    """Driver class for NI PXI 654x HSDIO card
    """
    def __init__(self, dev_name_str, logger=None):

        #
        # Define internal variables
        #

        self.log = LogHandler(logger=logger)
        self.map_dict = dict()
        self.writn_wfm_set = set()

        #
        # "Load" niHSDIO DLL
        #

        try:
            self.dll = ctypes.WinDLL(NI_HSDIO_DLL_PATH)
        except OSError:
            msg_str = 'DLL loading failed. \n' \
                      'Ensure that niHSDIO DLL path is correct: \n' \
                      'it should be specified in pylabnet.hardware.p_gen.ni_hsdio.__init__.py \n' \
                      'Current value is: \n' \
                      '"{}"'.format(NI_HSDIO_DLL_PATH)
            self.log.error(msg_str=msg_str)
            raise PGenError(msg_str)

        # Build C-prototypes (in particular, specify the return
        # types such that Python reads results correctly)
        build_c_func_prototypes(self.dll)

        #
        # Connect to device
        #

        # Create blank session handle
        self._handle = NITypes.ViSession()

        # Convert dev_name to the DLL-readable format
        self._dev_name = NITypes.ViRsrc(dev_name_str.encode('ascii'))

        self._er_chk(
            self.dll.niHSDIO_InitGenerationSession(
                self._dev_name,  # ViRsrc resourceName
                NIConst.VI_TRUE,  # ViBoolean IDQuery
                NIConst.VI_TRUE,  # ViBoolean resetDevice
                NIConst.
                VI_NULL,  # ViConstString optionString - not used, set to VI_NULL
                ctypes.byref(self._handle)  # ViSession * session_handle
            ))

        # Log info message
        serial_str = self._get_attr_str(NIConst.NIHSDIO_ATTR_SERIAL_NUMBER)
        self.log.info(
            'Connected to NI HSDIO card {0}. Serial number: {1}. Session handle: {2}'
            ''.format(dev_name_str, serial_str, self._handle))

    def reset(self):

        self.writn_wfm_set = set()
        self.map_dict = dict()

        return self._er_chk(self.dll.niHSDIO_reset(self._handle))

    def start(self):
        return self._er_chk(self.dll.niHSDIO_Initiate(self._handle))

    def stop(self):
        return self._er_chk(self.dll.niHSDIO_Abort(self._handle))

    def disconnect(self):
        self.reset()

        op_status = self._er_chk(self.dll.niHSDIO_close(self._handle))
        return op_status

    # ================================================================
    # Hardware settings
    # ================================================================

    def get_samp_rate(self):
        return self._get_attr_real64(NIConst.NIHSDIO_ATTR_SAMPLE_CLOCK_RATE)

    def set_samp_rate(self, samp_rate):

        # Sanity check
        if not samp_rate <= self.constraints['samp_rate']['max']:
            self.log.warn(
                'set_samp_rate({0} MHz): the requested value exceeds hardware constraint max={1} MHz.\n'
                'The max possible value will be set instead.'
                ''.format(samp_rate / 1e6,
                          self.constraints['samp_rate']['max'] / 1e6))
            samp_rate = self.constraints['samp_rate']['max']

        elif not self.constraints['samp_rate']['min'] <= samp_rate:
            self.log.warn(
                'set_samp_rate({0} Hz): the requested value is below the hardware constraint min={1} Hz.\n'
                'The min possible value will be set instead.'
                ''.format(samp_rate, self.constraints['samp_rate']['min']))
            samp_rate = self.constraints['samp_rate']['min']

        # Call DLL function
        # Currently, the onboard clock is used as the sample clock source
        self._er_chk(
            self.dll.niHSDIO_ConfigureSampleClock(
                self._handle,  # ViSession vi
                NIConst.
                NIHSDIO_VAL_ON_BOARD_CLOCK_STR,  # ViConstString clockSource
                NITypes.ViReal64(samp_rate)  # ViReal64 clockRate
            ))

        # Return the actual final sample rate
        return self.get_samp_rate()

    def get_active_chs(self):
        return self._get_attr_str(NIConst.NIHSDIO_ATTR_DYNAMIC_CHANNELS)

    def set_active_chs(self, chs_str=None):

        if chs_str is None:
            # un-assign all channels
            chs_str = 'none'

        chs_str = NITypes.ViString(chs_str.encode('ascii'))

        self._er_chk(
            self.dll.niHSDIO_AssignDynamicChannels(
                self._handle,  # ViSession vi,
                chs_str  # ViConstString channelList
            ))

        return self.get_active_chs()

    def get_mode(self):
        """

        :return: (str) "W" - Waveform, "S" - Scripted
        """

        mode_id = self._get_attr_int32(
            attr_id=NIConst.NIHSDIO_ATTR_GENERATION_MODE)

        if mode_id == NIConst.NIHSDIO_VAL_WAVEFORM.value:
            return 'W'
        elif mode_id == NIConst.NIHSDIO_VAL_SCRIPTED.value:
            return 'S'
        else:
            msg_str = 'get_mode(): self._get_attr_int32(NIHSDIO_ATTR_GENERATION_MODE) ' \
                      'returned unknown mode_id = {0}'.format(mode_id)
            self.log.error(msg_str)
            raise PGenError(msg_str)

    def set_mode(self, mode_string):
        """

        :param mode_string: (str) "W" - Waveform, "S" - Scripted
        :return: actual run mode string ("W"/"S")
        """

        if mode_string == 'W':
            run_mode = NIConst.NIHSDIO_VAL_WAVEFORM
        elif mode_string == 'S':
            run_mode = NIConst.NIHSDIO_VAL_SCRIPTED
        else:
            msg_str = 'set_mode({0}): invalid argument. Valid values: "W" - Waveform, "S" - scripted. \n' \
                      'Run mode was not changed. Actual run mode string was returned.'.format(mode_string)

            self.log.error(msg_str=msg_str)
            raise PGenError(msg_str)

        # Call DLL function
        self._er_chk(
            self.dll.niHSDIO_ConfigureGenerationMode(
                self._handle,  # ViSession vi
                run_mode  # ViInt32 generationMode
            ))

        # Return actual run mode
        return self.get_mode()

    @property
    def constraints(self):

        # Total memory size
        # [in samples; one sample contains 32 bits and covers all channels]
        max_wfm_len = self._get_attr_int32(
            attr_id=NIConst.NIHSDIO_ATTR_TOTAL_GENERATION_MEMORY_SIZE)

        constr_dict = dict(samp_rate=dict(min=48, max=100e6),
                           wfm_len=dict(min=2, step=2, max=max_wfm_len))

        return constr_dict

    def get_status(self):

        try:
            # Record current samp_rate to restore it later
            current_rate = self.get_samp_rate()
            rate_lims = self.constraints['samp_rate']

            test_rate = (rate_lims['min'] + rate_lims['max']) / 2

            # Try changing samp_rate
            op_status = self.dll.niHSDIO_ConfigureSampleClock(
                self._handle,  # ViSession vi
                NIConst.
                NIHSDIO_VAL_ON_BOARD_CLOCK_STR,  # ViConstString clockSource
                NITypes.ViReal64(test_rate)  # ViReal64 clockRate
            )

            # If device is idle, operation should be successful:
            #   op_status = 0.
            # Restore original samp rate and return 0 - "idle"

            if op_status == 0:
                self.set_samp_rate(samp_rate=current_rate)
                return 0

            # If device is running, attempt to change samp_rate should return
            # the following error code:
            #   -1074118617
            #   "Specified property cannot be set while the session is running.
            #   Set the property prior to initiating the session,
            #   or abort the session prior to setting the property."

            elif op_status == -1074118617:
                # Device is running
                return 1

            # This method cannot interpret any other error/warning code and has
            # to raise an exception

            else:
                raise PGenError(
                    'get_status(): the attempt to test-change samp_rate returned unknown error code {}'
                    ''.format(op_status))

        # If connection to the device is lost
        # or any operation fails, raise an exception.

        except Exception as exc_obj:
            self.log.exception(
                'get_status(): an exception was produced. \n'
                'This might mean that connection to the device is lost '
                'or there is some bug in the get_status() method. \n')
            raise exc_obj

    # ================================================================
    # Waveform Generation
    # ================================================================

    def write_wfm(self, pb_obj, len_adj=True):

        #
        # Sanity checks
        #

        # Only data_width=32 write is currently implemented
        # (DLL function niHSDIO_WriteNamedWaveformU32)
        hrdw_data_width = 8 * self._get_attr_int32(
            NIConst.NIHSDIO_ATTR_DATA_WIDTH)
        if hrdw_data_width != 32:
            msg_txt = 'write_wfm(): the card you use has data_width = {0} bits. \n' \
                      'The method was written assuming 32-bit width and have to be modified for your card. \n' \
                      'Rewrite bit_ar construction part and use niHSDIO_WriteNamedWaveformU{1}() DLL function' \
                      ''.format(hrdw_data_width, hrdw_data_width)

            self.log.error(msg_txt)
            raise PGenError(msg_txt)

        #
        # Sample PulseBlock
        #

        # Map user-friendly names onto physical channel numbers
        pb_obj = copy.deepcopy(pb_obj)
        pb_obj.ch_map(map_dict=self.map_dict)

        # Sample pulse block
        samp_rate = self.get_samp_rate()
        samp_dict, n_pts, add_pts = pb_sample(
            pb_obj=pb_obj,
            samp_rate=samp_rate,
            len_min=self.constraints['wfm_len']['min'],
            len_max=self.constraints['wfm_len']['max'],
            len_step=self.constraints['wfm_len']['step'],
            len_adj=len_adj)
        wfm_name = pb_obj.name
        del pb_obj

        self.log.info(
            'write_wfm(): sampled PulseBlock "{}". \n'
            'Sample array has {} points. {} samples were added to match hardware wfm len step'
            ''.format(wfm_name, n_pts, add_pts))

        #
        # Pack samp_dict into bit_ar
        #

        # Create a blank bit_ar - all elements zero (all channels off)
        bit_ar = np.zeros(shape=n_pts, dtype=np.uint32)

        # Iterate through each channel and set corresponding bit to '1'
        # if value is True
        for ch_num in samp_dict.keys():

            # This number in uint32 representation has all zeros and
            # exactly one '1' at the ch_num-th bit from the LSB
            ch_bit_one = 2**ch_num

            for idx, val in enumerate(samp_dict[ch_num]):
                if val:
                    bit_ar[idx] += ch_bit_one

            # TODO: consider making very fast with numpy:
            # ch_bit_ar = samp_dict[ch_num].astype(int) * 2**ch_num
            # bit_ar = np.add(bit_ar, ch_bit_ar)

        #
        # Load bit_ar to memory
        #

        # Delete waveform with the same name,
        # if it is already present in the memory
        if wfm_name in self.writn_wfm_set:
            self.del_wfm(wfm_name=wfm_name)

        # Create C-pointer to bit_ar using numpy.ndarray.ctypes attribute
        bit_ar_ptr = bit_ar.ctypes.data_as(ctypes.POINTER(NITypes.ViUInt32))

        # Call DLL function
        self._er_chk(
            self.dll.niHSDIO_WriteNamedWaveformU32(
                self._handle,  # ViSession vi
                NITypes.ViConstString(
                    wfm_name.encode('ascii')),  # ViConstString waveformName
                NITypes.ViInt32(n_pts),  # ViInt32 samplesToWrite
                bit_ar_ptr  # ViUInt32 data[]
            ))

        self.writn_wfm_set.add(wfm_name)

        return 0

    def del_wfm(self, wfm_name):
        self._er_chk(
            self.dll.niHSDIO_DeleteNamedWaveform(
                self._handle,  # ViSession vi
                NITypes.ViConstString(
                    wfm_name.encode('ascii'))  # ViConstString waveformName
            ))

        self.writn_wfm_set.remove(wfm_name)

        return 0

    def clr_mem(self):
        wfm_set = copy.deepcopy(self.writn_wfm_set)

        for wfm_name in wfm_set:
            self.del_wfm(wfm_name=wfm_name)

        return 0

    def get_rep(self):
        """Returns number of repetitions in Waveform generation mode.

        On the hardware level, it is just a pair of attributes
        NIHSDIO_ATTR_REPEAT_MODE and NIHSDIO_ATTR_REPEAT_COUNT
        which are not bound to any specific waveform.

        :return: (int) repeat mode + number of repetitions:
                 0 - repeat infinitely
                 positive integer - finite, number of repetitions
                 PGenError exception - error
        """

        rep_mode = self._get_attr_int32(NIConst.NIHSDIO_ATTR_REPEAT_MODE)

        if rep_mode == NIConst.NIHSDIO_VAL_CONTINUOUS.value:
            rep_num = 0
        elif rep_mode == NIConst.NIHSDIO_VAL_FINITE.value:
            rep_num = self._get_attr_int32(NIConst.NIHSDIO_ATTR_REPEAT_COUNT)
        else:
            msg_str = 'get_rep(): DLL call returned unknown repetition mode code {}'.format(
                rep_mode)

            self.log.error(msg_str=msg_str)
            raise PGenError(msg_str)

        return rep_num

    def set_rep(self, rep_num):
        """Set repeat mode and number of repetitions

        :param rep_num: (int) repeat mode + number of repetitions:
                    0 - repeat infinitely
                    positive integer - finite, number of repetitions

        :return: (int) actual repeat mode + number of repetitions:
                    0 - repeat infinitely
                    positive integer - finite, number of repetitions
                    PGenError exception - error
        """

        if rep_num == 0:
            rep_mode = NIConst.NIHSDIO_VAL_CONTINUOUS
            rep_num = NIConst.VI_NULL

        elif rep_num > 0:
            rep_mode = NIConst.NIHSDIO_VAL_FINITE
            rep_num = NITypes.ViInt32(rep_num)

        else:
            msg_str = 'set_rep() invalid argument {} \n' \
                      'Valid values: 0 - infinite, positive integer - finite' \
                      ''.format(rep_num)

            self.log.error(msg_str=msg_str)
            raise PGenError(msg_str)

        self._er_chk(
            self.dll.niHSDIO_ConfigureGenerationRepeat(
                self._handle,  # ViSession vi
                rep_mode,  # ViInt32 repeatMode
                rep_num  # ViInt32 repeatCount
            ))

        return self.get_rep()

    def get_wfm_to_gen(self):
        return self._get_attr_str(NIConst.NIHSDIO_ATTR_WAVEFORM_TO_GENERATE)

    def set_wfm_to_gen(self, wfm_name):
        self._er_chk(
            self.dll.niHSDIO_ConfigureWaveformToGenerate(
                self._handle,  # ViSession vi
                NITypes.ViConstString(
                    wfm_name.encode('ascii'))  # ViConstString waveformName
            ))
        return self.get_wfm_to_gen()

    # ================================================================
    # Script Generation
    # ================================================================

    def write_script(self, script_str):

        # Sanity check: script_str is a string
        if not isinstance(script_str, str):
            msg_str = 'write_script(): passed argument is not a string'

            self.log.error(msg_str=msg_str)
            raise PGenError(msg_str)

        plain_script_str = script_str.replace('\n', ' ')

        # Convert into C-string
        c_script_str = NITypes.ViConstString(plain_script_str.encode('ascii'))

        op_status = self._er_chk(
            self.dll.niHSDIO_WriteScript(
                self._handle,  # ViSession vi
                c_script_str  # ViConstString script
            ))

        return op_status

    def get_scr_to_gen(self):
        return self._get_attr_str(NIConst.NIHSDIO_ATTR_SCRIPT_TO_GENERATE)

    def set_scr_to_gen(self, script_name):

        # Convert script_name into C-string
        script_name = NITypes.ViConstString(script_name.encode('ascii'))

        self._er_chk(
            self.dll.niHSDIO_ConfigureScriptToGenerate(
                self._handle,  # ViSession vi
                script_name  # ViConstString scriptName
            ))

        return self.get_scr_to_gen()

    # ================================================================
    # Wrappers for C DLL helper functions
    # ================================================================

    def _er_chk(self, error_code):
        # C:\Program Files\National Instruments\Shared\Errors\English\IVI-errors.txt

        if error_code == 0:
            # Success
            return 0
        else:
            # Warning or Error

            # Create buffer for DLL function to output the error message string
            msg_buf = ctypes.create_string_buffer(256)

            # Call DLL function to generate readable error message
            self.dll.niHSDIO_error_message(
                self._handle,  # ViSession vi
                NITypes.ViStatus(error_code),  # ViStatus errorCode
                msg_buf  # ViChar errorMessage[256]
            )
            msg_str = msg_buf.value.decode('ascii')

            if error_code > 0:
                # Warning
                self.log.warn(msg_str=msg_str)
                return error_code
            else:
                # Error
                self.log.error(msg_str=msg_str)
                raise PGenError(msg_str)

    def _get_attr_int32(self, attr_id, ch=None):
        """

        :param attr_id:
        :param ch: (int)
        :return: (int) obtained value in the case of success
                 PGenError exception is produced in the case of error
        """

        # Create buffer where niHSDIO_GetAttribute will store the result
        buf = NITypes.ViInt32()

        # Convert channel number into C-string
        # (used to request channel-agnostic/-specific attributes)
        if ch is None:
            ch_str = NIConst.VI_NULL
        else:
            ch_str = str(ch)
            # Convert into C-string
            ch_str = ctypes.c_char_p(ch_str.encode('ascii'))

        # Call DLL function
        try:
            self._er_chk(
                self.dll.niHSDIO_GetAttributeViInt32(
                    self._handle,  # ViSession vi
                    ch_str,  # ViConstString channelName
                    attr_id,  # ViAttr attribute
                    ctypes.byref(buf)  # ViInt32 *value
                ))
            return buf.value

        except OSError:
            # DLL normally handles all "moderate" errors and returns error code,
            # which is being analyzed by self._er_chk.
            # "try" handles OSError when the DLL function fails completely
            # and isn't able to handle the error by itself

            msg_str = '_get_attr_int32(): OSError, DLL function call failed'
            self.log.error(msg_str=msg_str)
            raise PGenError(msg_str)

    def _get_attr_str(self, attr_id, ch=None):
        """

        :param attr_id:
        :param ch: (int)
        :return: (str) obtained value in the case of success
                       Exception is produced in the case of error
        """

        # Create buffer where niHSDIO_GetAttribute will store the result
        buf_size = 1024  # Buffer size of 1024 was chosen for no specific reason. Increase if needed.
        buf = ctypes.create_string_buffer(buf_size)

        # Convert channel number into C-string
        # (used to request channel-agnostic/-specific attributes)
        if ch is None:
            ch_str = NIConst.VI_NULL
        else:
            ch_str = str(ch)
            # Convert into C-string
            ch_str = ctypes.c_char_p(ch_str.encode('ascii'))

        # Call DLL function
        try:
            self._er_chk(
                self.dll.niHSDIO_GetAttributeViString(
                    self._handle,  # ViSession vi
                    ch_str,  # ViConstString channelName
                    attr_id,  # ViAttr attribute
                    NITypes.ViInt32(buf_size),  # ViInt32 bufSize
                    buf  # ViChar value[]
                ))
            return buf.value.decode('ascii')

        except OSError:
            # DLL normally handles all "moderate" errors and returns error code,
            # which is being analyzed by self._er_chk.
            # "try" handles OSError when the DLL function fails completely
            # and isn't able to handle the error by itself

            msg_str = '_get_attr_str(): OSError, DLL function call failed'
            self.log.error(msg_str=msg_str)
            raise PGenError(msg_str)

    def _get_attr_real64(self, attr_id, ch=None):
        """

        :param attr_id:
        :param ch: (int)
        :return: (float) obtained value in the case of success
                 Exception is produced in the case of error
        """

        # Create buffer where niHSDIO_GetAttribute will store the result
        buf = NITypes.ViReal64()

        # Convert channel number into C-string
        # (used to request channel-agnostic/-specific attributes)
        if ch is None:
            ch_str = NIConst.VI_NULL
        else:
            ch_str = str(ch)
            # Convert into C-string
            ch_str = ctypes.c_char_p(ch_str.encode('ascii'))

        # Call DLL function
        try:
            self._er_chk(
                self.dll.niHSDIO_GetAttributeViReal64(
                    self._handle,  # ViSession vi
                    ch_str,  # ViConstString channelName
                    attr_id,  # ViAttr attribute
                    ctypes.byref(buf)  # ViReal64 *value
                ))
            return buf.value

        except OSError:
            # DLL normally handles all "moderate" errors and returns error code,
            # which is being analyzed by self._er_chk.
            # "try" handles OSError when the DLL function fails completely
            # and isn't able to handle the error by itself

            msg_str = '_get_attr_real64(): OSError, DLL function call failed'
            self.log.error(msg_str=msg_str)
            raise PGenError(msg_str)
Beispiel #30
0
class Driver():
    def __init__(self, name, logger, hardware_client, hardware_type, config):
        '''High level staticline class.

        This class is used in conjunction with hardware modules to send out digital
        signals ('voltage low' and 'voltage high'). This top level class is hardware agnostic.
        With the use of a StaticLineHardwareHandler, this class will be associated
        with the necessary setup functions and output functions of a hardware module.

        :name:(str)
            A easily recognizable name for this staticline, ideally referring to
            the device being controlled by it, e.g. 'Shutter 1'.
        :logger: (object)
            An instance of a LogClient.
        :hardware_client: (object)
            An instance of a hardware Client.
        :hardware_type: (str)
            Name of the hardware to be controlled, naming is determined by the
            device server name.
        :config: (dict)
            Contains parameters needed to setup the hardware as a staticline.
        '''

        self.name = name
        self.log = LogHandler(logger=logger)

        # Check that the provided class is a valid StaticLine class
        if hardware_type not in registered_staticline_modules:
            self.log.error(
                f"Setup of staticline using module {hardware_type} failed.\n"
                f"Compatible modules are: {registered_staticline_modules.keys()}"
            )

        # Acquire the correct handler for the hardware type
        HardwareHandler = registered_staticline_modules[hardware_type]

        # Instantiate hardware handler. The hardware_handler will handle any
        # calls to the staticline functions like up/down.
        self.hardware_handler = HardwareHandler(
            name=name,
            log=self.log,
            hardware_client=hardware_client,
            config=config)

    def up(self):
        '''Set output to high.'''
        self.hardware_handler.up()
        self.log.info(f"Staticline {self.name} set to high.")

    def down(self):
        '''Set output to low.'''
        self.hardware_handler.down()
        self.log.info(f"Staticline {self.name} set to low.")

    def set_dig_value(self, value):
        '''Sets output level for adjustable digital values'''
        self.hardware_handler.set_dig_value(value)
        self.log.info(
            f"Staticline {self.name} adjustable output set to {value}.")

    def set_value(self, value):
        '''Set output to a specified value.'''
        self.hardware_handler.set_value(value)
        self.log.info(f"Staticline {self.name} set to {value}.")

    def get_name(self):
        return self.name