Esempio n. 1
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)
Esempio n. 2
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)
Esempio n. 3
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)
Esempio n. 4
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)
Esempio n. 5
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)
Esempio n. 6
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
Esempio n. 7
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
Esempio n. 8
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
Esempio n. 9
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')
Esempio n. 10
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
Esempio n. 11
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)
Esempio n. 12
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
Esempio n. 13
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?'))