Esempio n. 1
0
    def on_load_clicked(self):
        """Show GUI to load sweep data from file."""
        prompt = 'Please select a data file.'
        filepath, _ = QtWidgets.QFileDialog.getOpenFileName(self, prompt)
        if not osp.isfile(filepath):
            return

        self.sweep_data = FETResultTable()
        self.sweep_data.load(filepath)

        self.canvas.plot(self.sweep_data)
        self.actionSaveSweepData.setEnabled(True)
Esempio n. 2
0
    def run(self):
        self.started_sig.emit()
        sweep_data = None

        if self.params["sweep_type"] == "transfer":
            sweep_data = self.keithley.transferMeasurement(
                self.params["smu_gate"],
                self.params["smu_drain"],
                self.params["VgStart"],
                self.params["VgStop"],
                self.params["VgStep"],
                self.params["VdList"],
                self.params["tInt"],
                self.params["delay"],
                self.params["pulsed"],
            )
        elif self.params["sweep_type"] == "output":
            sweep_data = self.keithley.outputMeasurement(
                self.params["smu_gate"],
                self.params["smu_drain"],
                self.params["VdStart"],
                self.params["VdStop"],
                self.params["VdStep"],
                self.params["VgList"],
                self.params["tInt"],
                self.params["delay"],
                self.params["pulsed"],
            )

        elif self.params["sweep_type"] == "iv":
            direction = np.sign(self.params["VStop"] - self.params["VStart"])
            stp = direction * abs(self.params["VStep"])
            sweeplist = np.arange(self.params["VStart"],
                                  self.params["VStop"] + stp, stp)
            v, i = self.keithley.voltageSweepSingleSMU(
                self.params["smu_sweep"],
                sweeplist,
                self.params["tInt"],
                self.params["delay"],
                self.params["pulsed"],
            )

            self.keithley.beeper.beep(0.3, 2400)
            self.keithley.reset()

            params = {
                "sweep_type": "iv",
                "t_int": self.params["tInt"],
                "delay": self.params["delay"],
                "pulsed": self.params["pulsed"],
            }

            sweep_data = FETResultTable(["Voltage", "Current"], ["V", "A"],
                                        np.array([v, i]).transpose(), params)

        self.finished_sig.emit(sweep_data)
Esempio n. 3
0
    def run(self):
        self.started_sig.emit()
        sweep_data = None

        if self.params['sweep_type'] == 'transfer':
            sweep_data = self.keithley.transferMeasurement(
                self.params['smu_gate'], self.params['smu_drain'],
                self.params['VgStart'], self.params['VgStop'],
                self.params['VgStep'], self.params['VdList'],
                self.params['tInt'], self.params['delay'],
                self.params['pulsed'])
        elif self.params['sweep_type'] == 'output':
            sweep_data = self.keithley.outputMeasurement(
                self.params['smu_gate'], self.params['smu_drain'],
                self.params['VdStart'], self.params['VdStop'],
                self.params['VdStep'], self.params['VgList'],
                self.params['tInt'], self.params['delay'],
                self.params['pulsed'])

        elif self.params['sweep_type'] == 'iv':
            direction = np.sign(self.params['VStop'] - self.params['VStart'])
            stp = direction * abs(self.params['VStep'])
            sweeplist = np.arange(self.params['VStart'],
                                  self.params['VStop'] + stp, stp)
            v, i = self.keithley.voltageSweepSingleSMU(
                self.params['smu_sweep'], sweeplist, self.params['tInt'],
                self.params['delay'], self.params['pulsed'])

            self.keithley.beeper.beep(0.3, 2400)
            self.keithley.reset()

            params = {
                'sweep_type': 'iv',
                't_int': self.params['tInt'],
                'delay': self.params['delay'],
                'pulsed': self.params['pulsed']
            }

            sweep_data = FETResultTable(['Voltage', 'Current'], ['V', 'A'],
                                        np.array([v, i]).transpose(), params)

        self.finished_sig.emit(sweep_data)
Esempio n. 4
0
class KeithleyGuiApp(QtWidgets.QMainWindow):
    """ Provides a GUI for transfer and output sweeps on the Keithley 2600."""

    QUIT_ON_CLOSE = True

    def __init__(self, keithley):
        super(self.__class__, self).__init__()
        # load user interface layout from .ui file
        uic.loadUi(MAIN_UI_PATH, self)

        self.keithley = keithley
        self.smu_list = list(self.keithley.SMU_LIST)
        self.sweep_data = None

        # create sweep settings panes
        self.transfer_sweep_settings = TransferSweepSettingsWidget()
        self.output_sweep_settings = OutputSweepSettingsWidget()
        self.iv_sweep_settings = IVSweepSettingsWidget(self.smu_list)
        self.general_sweep_settings = SweepSettingsWidget(self.keithley)

        self.tabWidgetSweeps.widget(0).layout().addWidget(
            self.transfer_sweep_settings)
        self.tabWidgetSweeps.widget(1).layout().addWidget(
            self.output_sweep_settings)
        self.tabWidgetSweeps.widget(2).layout().addWidget(
            self.iv_sweep_settings)
        self.groupBoxSweepSettings.layout().addWidget(
            self.general_sweep_settings)

        # create tabs for smu settings
        self.smu_tabs = []
        for smu_name in self.smu_list:
            tab = SMUSettingsWidget(smu_name)
            self.tabWidgetSettings.addTab(tab, smu_name)
            self.smu_tabs.append(tab)

        # create plot widget
        self.canvas = SweepDataPlot()
        self.gridLayout2.addWidget(self.canvas)

        # create LED indicator
        self.led = LedIndicator(self)
        self.statusBar.addPermanentWidget(self.led)
        self.led.setChecked(False)

        # create connection dialog
        self.connectionDialog = ConnectionDialog(self, self.keithley)

        # restore last position and size
        self.restore_geometry()

        # update GUI status and connect callbacks
        self.actionSaveSweepData.setEnabled(False)
        self.connect_ui_callbacks()
        self.on_load_default()
        self.update_gui_connection()

        # connection update timer: check periodically if keithley is connected
        # and busy, act accordingly
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.update_gui_connection)
        self.timer.start(10000)

    @staticmethod
    def _string_to_vd(string):
        try:
            return float(string)
        except ValueError:
            if 'trailing' in string:
                return 'trailing'
            else:
                raise ValueError('Invalid drain voltage.')

    def closeEvent(self, event):
        if self.QUIT_ON_CLOSE:
            self.exit_()
        else:
            self.hide()

# =============================================================================
# GUI setup
# =============================================================================

    def restore_geometry(self):
        x = CONF.get('Window', 'x')
        y = CONF.get('Window', 'y')
        w = CONF.get('Window', 'width')
        h = CONF.get('Window', 'height')

        self.setGeometry(x, y, w, h)

    def save_geometry(self):
        geo = self.geometry()
        CONF.set('Window', 'height', geo.height())
        CONF.set('Window', 'width', geo.width())
        CONF.set('Window', 'x', geo.x())
        CONF.set('Window', 'y', geo.y())

    def connect_ui_callbacks(self):
        """Connect buttons and menus to callbacks."""
        self.pushButtonRun.clicked.connect(self.on_sweep_clicked)
        self.pushButtonAbort.clicked.connect(self.on_abort_clicked)

        self.actionSettings.triggered.connect(self.connectionDialog.open)
        self.actionConnect.triggered.connect(self.on_connect_clicked)
        self.actionDisconnect.triggered.connect(self.on_disconnect_clicked)
        self.action_Exit.triggered.connect(self.exit_)
        self.actionSaveSweepData.triggered.connect(self.on_save_clicked)
        self.actionLoad_data_from_file.triggered.connect(self.on_load_clicked)
        self.actionSaveDefaults.triggered.connect(self.on_save_default)
        self.actionLoadDefaults.triggered.connect(self.on_load_default)

# =============================================================================
# Measurement callbacks
# =============================================================================

    def apply_smu_settings(self):
        """
        Applies SMU settings to Keithley before a measurement.
        Warning: self.keithley.reset() will reset those settings.
        """
        for tab in self.smu_tabs:

            smu = getattr(self.keithley, tab.smu_name)

            if tab.sense_type.currentIndex() == tab.SENSE_LOCAL:
                smu.sense = smu.SENSE_LOCAL
            elif tab.sense_type.currentIndex() == tab.SENSE_REMOTE:
                smu.sense = smu.SENSE_REMOTE

            lim_i = tab.limit_i.value()
            smu.source.limiti = lim_i
            smu.trigger.source.limiti = lim_i

            lim_v = tab.limit_v.value()
            smu.source.limitv = lim_v
            smu.trigger.source.limitv = lim_v

    @QtCore.Slot()
    def on_sweep_clicked(self):
        """ Start a transfer measurement with current settings."""

        if self.keithley.busy:
            msg = ('Keithley is currently used by another program. ' +
                   'Please try again later.')
            QtWidgets.QMessageBox.information(self, str('error'), msg)

            return

        self.apply_smu_settings()

        params = dict()

        if self.tabWidgetSweeps.currentIndex() == 0:
            self.statusBar.showMessage('    Recording transfer curve.')
            # get sweep settings
            params['sweep_type'] = 'transfer'
            params['VgStart'] = self.transfer_sweep_settings.vg_start.value()
            params['VgStop'] = self.transfer_sweep_settings.vg_stop.value()
            params['VgStep'] = self.transfer_sweep_settings.vg_step.value()
            params['VdList'] = self.transfer_sweep_settings.vd_list.value()

        elif self.tabWidgetSweeps.currentIndex() == 1:
            self.statusBar.showMessage('    Recording output curve.')
            # get sweep settings
            params['sweep_type'] = 'output'
            params['VdStart'] = self.output_sweep_settings.vd_start.value()
            params['VdStop'] = self.output_sweep_settings.vd_stop.value()
            params['VdStep'] = self.output_sweep_settings.vd_step.value()
            params['VgList'] = self.output_sweep_settings.vg_list.value()

        elif self.tabWidgetSweeps.currentIndex() == 2:
            self.statusBar.showMessage('    Recording IV curve.')
            # get sweep settings
            params['sweep_type'] = 'iv'
            params['VStart'] = self.iv_sweep_settings.v_start.value()
            params['VStop'] = self.iv_sweep_settings.v_stop.value()
            params['VStep'] = self.iv_sweep_settings.v_step.value()
            smusweep = self.iv_sweep_settings.smu_sweep.currentText()
            params['smu_sweep'] = getattr(self.keithley, smusweep)

        else:
            return

        # get general sweep settings
        smu_gate = self.general_sweep_settings.smu_gate.currentText()
        smu_drain = self.general_sweep_settings.smu_drain.currentText()
        params['tInt'] = self.general_sweep_settings.t_int.value()
        params['delay'] = self.general_sweep_settings.t_settling.value()
        params['smu_gate'] = getattr(self.keithley, smu_gate)
        params['smu_drain'] = getattr(self.keithley, smu_drain)
        params['pulsed'] = bool(
            self.general_sweep_settings.sweep_type.currentIndex())

        # check if integration time is valid, return otherwise
        freq = self.keithley.localnode.linefreq

        if not 0.001 / freq < params['tInt'] < 25.0 / freq:
            msg = ('Integration time must be between 0.001 and 25 ' +
                   'power line cycles of 1/(%s Hz).' % freq)
            QtWidgets.QMessageBox.information(self, str('error'), msg)

            return

        # create measurement thread with params dictionary
        self.measureThread = MeasureThread(self.keithley, params)
        self.measureThread.finished_sig.connect(self.on_measure_done)

        # run measurement
        self._gui_state_busy()
        self.measureThread.start()

    def on_measure_done(self, sd):
        self.statusBar.showMessage('    Ready.')
        self._gui_state_idle()
        self.actionSaveSweepData.setEnabled(True)

        self.sweep_data = sd
        self.canvas.plot(self.sweep_data)
        if not self.keithley.abort_event.is_set():
            self.on_save_clicked()

    @QtCore.Slot()
    def on_abort_clicked(self):
        """
        Aborts current measurement.
        """
        self.keithley.abort_event.set()

# =============================================================================
# Interface callbacks
# =============================================================================

    @QtCore.Slot()
    def on_connect_clicked(self):
        self.keithley.connect()
        self.update_gui_connection()
        if not self.keithley.connected:
            msg = ('Keithley cannot be reached at %s. ' %
                   self.keithley.visa_address +
                   'Please check if address is correct and Keithley is ' +
                   'turned on.')
            QtWidgets.QMessageBox.information(self, str('error'), msg)

    @QtCore.Slot()
    def on_disconnect_clicked(self):
        self.keithley.disconnect()
        self.update_gui_connection()
        self.statusBar.showMessage('    No Keithley connected.')

    @QtCore.Slot()
    def on_save_clicked(self):
        """Show GUI to save current sweep data as text file."""
        prompt = 'Save as .txt file.'
        filename = 'untitled.txt'
        formats = 'Text file (*.txt)'
        filepath, _ = QtWidgets.QFileDialog.getSaveFileName(
            self, prompt, filename, formats)
        if len(filepath) < 4:
            return
        self.sweep_data.save(filepath)

    @QtCore.Slot()
    def on_load_clicked(self):
        """Show GUI to load sweep data from file."""
        prompt = 'Please select a data file.'
        filepath, _ = QtWidgets.QFileDialog.getOpenFileName(self, prompt)
        if not osp.isfile(filepath):
            return

        self.sweep_data = FETResultTable()
        self.sweep_data.load(filepath)

        self.canvas.plot(self.sweep_data)
        self.actionSaveSweepData.setEnabled(True)

    @QtCore.Slot()
    def on_save_default(self):
        """Saves current settings from GUI as defaults."""

        # save sweep settings
        self.transfer_sweep_settings.save_defaults()
        self.output_sweep_settings.save_defaults()
        self.iv_sweep_settings.save_defaults()
        self.general_sweep_settings.save_defaults()

        # save smu specific settings
        for tab in self.smu_tabs:
            tab.save_defaults()

    @QtCore.Slot()
    def on_load_default(self):
        """Load default settings to interface."""

        # load sweep settings
        self.transfer_sweep_settings.load_defaults()
        self.output_sweep_settings.load_defaults()
        self.iv_sweep_settings.load_defaults()
        self.general_sweep_settings.load_defaults()

        # smu settings
        for tab in self.smu_tabs:
            tab.load_defaults()

    @QtCore.Slot()
    def exit_(self):
        self.keithley.disconnect()
        self.timer.stop()
        self.save_geometry()
        self.deleteLater()


# =============================================================================
# Interface states
# =============================================================================

    def update_gui_connection(self):
        """Check if Keithley is connected and update GUI."""
        if self.keithley.connected and not self.keithley.busy:
            try:
                test = self.keithley.localnode.model
                self._gui_state_idle()
            except (visa.VisaIOError, visa.InvalidSession, OSError):
                self.keithley.disconnect()
                self._gui_state_disconnected()

        elif self.keithley.connected and self.keithley.busy:
            self._gui_state_busy()

        elif not self.keithley.connected:
            self._gui_state_disconnected()

    def _gui_state_busy(self):
        """Set GUI to state for running measurement."""

        self.pushButtonRun.setEnabled(False)
        self.pushButtonAbort.setEnabled(True)

        self.actionConnect.setEnabled(False)
        self.actionDisconnect.setEnabled(False)

        self.statusBar.showMessage('    Measuring.')
        self.led.setChecked(True)

    def _gui_state_idle(self):
        """Set GUI to state for IDLE Keithley."""

        self.pushButtonRun.setEnabled(True)
        self.pushButtonAbort.setEnabled(False)

        self.actionConnect.setEnabled(False)
        self.actionDisconnect.setEnabled(True)
        self.statusBar.showMessage('    Ready.')
        self.led.setChecked(True)

    def _gui_state_disconnected(self):
        """Set GUI to state for disconnected Keithley."""

        self.pushButtonRun.setEnabled(False)
        self.pushButtonAbort.setEnabled(False)

        self.actionConnect.setEnabled(True)
        self.actionDisconnect.setEnabled(False)
        self.statusBar.showMessage('    No Keithley connected.')
        self.led.setChecked(False)
Esempio n. 5
0
    def run(self):

        self.started_sig.emit()

        try:
            sweep_data = None

            if self.params["sweep_type"] == "transfer":
                sweep_data = self.keithley.transfer_measurement(
                    self.params["smu_gate"],
                    self.params["smu_drain"],
                    self.params["VgStart"],
                    self.params["VgStop"],
                    self.params["VgStep"],
                    self.params["VdList"],
                    self.params["tInt"],
                    self.params["delay"],
                    self.params["pulsed"],
                )
            elif self.params["sweep_type"] == "output":
                sweep_data = self.keithley.output_measurement(
                    self.params["smu_gate"],
                    self.params["smu_drain"],
                    self.params["VdStart"],
                    self.params["VdStop"],
                    self.params["VdStep"],
                    self.params["VgList"],
                    self.params["tInt"],
                    self.params["delay"],
                    self.params["pulsed"],
                )

            elif self.params["sweep_type"] == "iv":
                direction = np.sign(self.params["VStop"] - self.params["VStart"])
                stp = direction * abs(self.params["VStep"])

                # forward and reverse sweeps
                sweeplist = np.arange(
                    self.params["VStart"], self.params["VStop"] + stp, stp
                )
                sweeplist = np.append(sweeplist, np.flip(sweeplist))

                v, i = self.keithley.voltage_sweep_single_smu(
                    self.params["smu_sweep"],
                    sweeplist,
                    self.params["tInt"],
                    self.params["delay"],
                    self.params["pulsed"],
                )

                params = {
                    "sweep_type": "iv",
                    "t_int": self.params["tInt"],
                    "delay": self.params["delay"],
                    "pulsed": self.params["pulsed"],
                }

                sweep_data = FETResultTable(
                    column_titles=["Voltage", "Current"],
                    units=["V", "A"],
                    data=np.array([v, i]).transpose(),
                    params=params,
                )

            self.keithley.beeper.beep(0.3, 2400)
            self.keithley.reset()

            self.finished_sig.emit(sweep_data)
        except Exception as exc:
            self.error_sig.emit(exc)
Esempio n. 6
0
class KeithleyGuiApp(QtWidgets.QMainWindow):
    """ Provides a GUI for transfer and output sweeps on the Keithley 2600."""

    QUIT_ON_CLOSE = True

    def __init__(self, keithley=None):
        super().__init__()
        # load user interface layout from .ui file
        uic.loadUi(MAIN_UI_PATH, self)

        if keithley:
            self.keithley = keithley
        else:
            address = CONF.get("Connection", "VISA_ADDRESS")
            lib = CONF.get("Connection", "VISA_LIBRARY")
            self.keithley = Keithley2600(address, lib)

        self.smu_list = _get_smus(self.keithley)
        self.sweep_data = None

        # create sweep settings panes
        self.transfer_sweep_settings = TransferSweepSettingsWidget()
        self.output_sweep_settings = OutputSweepSettingsWidget()
        self.iv_sweep_settings = IVSweepSettingsWidget(self.keithley)
        self.general_sweep_settings = SweepSettingsWidget(self.keithley)

        self.tabWidgetSweeps.widget(0).layout().addWidget(self.transfer_sweep_settings)
        self.tabWidgetSweeps.widget(1).layout().addWidget(self.output_sweep_settings)
        self.tabWidgetSweeps.widget(2).layout().addWidget(self.iv_sweep_settings)
        self.groupBoxSweepSettings.layout().addWidget(self.general_sweep_settings)

        # create tabs for smu settings
        self.smu_tabs = []
        for smu_name in self.smu_list:
            tab = SMUSettingsWidget(smu_name)
            self.tabWidgetSettings.addTab(tab, smu_name)
            self.smu_tabs.append(tab)

        # create plot widget
        self.canvas = SweepDataPlot()
        self.gridLayout2.addWidget(self.canvas)

        # create LED indicator
        self.led = LedIndicator(self)
        self.statusBar.addPermanentWidget(self.led)
        self.led.setChecked(False)

        # create connection dialog
        self.connectionDialog = ConnectionDialog(self, self.keithley, CONF)

        # restore last position and size
        self.restore_geometry()

        # update GUI status and connect callbacks
        self.actionSaveSweepData.setEnabled(False)
        self.connect_ui_callbacks()
        self.on_load_default()
        self.update_gui_connection()

        # connection update timer: check periodically if keithley is connected
        self.connection_status_update = QtCore.QTimer()
        self.connection_status_update.timeout.connect(self.update_gui_connection)
        self.connection_status_update.start(10000)  # 10 sec

    def update_smu_list(self):

        self.smu_list = _get_smus(self.keithley)

        # recreate tabs for smu settings
        self.smu_tabs = []
        self.tabWidgetSettings.clear()
        for smu_name in self.smu_list:
            tab = SMUSettingsWidget(smu_name)
            self.tabWidgetSettings.addTab(tab, smu_name)
            self.smu_tabs.append(tab)

        self.iv_sweep_settings.update_smu_list()
        self.general_sweep_settings.update_smu_list()

    @staticmethod
    def _string_to_vd(string):
        try:
            return float(string)
        except ValueError:
            if "trailing" in string:
                return "trailing"
            else:
                raise ValueError("Invalid drain voltage.")

    def closeEvent(self, event):
        if self.QUIT_ON_CLOSE:
            self.exit_()
        else:
            self.hide()

    # =============================================================================
    # GUI setup
    # =============================================================================

    def restore_geometry(self):
        x = CONF.get("Window", "x")
        y = CONF.get("Window", "y")
        w = CONF.get("Window", "width")
        h = CONF.get("Window", "height")

        self.setGeometry(x, y, w, h)

    def save_geometry(self):
        geo = self.geometry()
        CONF.set("Window", "height", geo.height())
        CONF.set("Window", "width", geo.width())
        CONF.set("Window", "x", geo.x())
        CONF.set("Window", "y", geo.y())

    def connect_ui_callbacks(self):
        """Connect buttons and menus to callbacks."""
        self.pushButtonRun.clicked.connect(self.on_sweep_clicked)
        self.pushButtonAbort.clicked.connect(self.on_abort_clicked)

        self.actionSettings.triggered.connect(self.connectionDialog.open)
        self.actionConnect.triggered.connect(self.on_connect_clicked)
        self.actionDisconnect.triggered.connect(self.on_disconnect_clicked)
        self.action_Exit.triggered.connect(self.exit_)
        self.actionSaveSweepData.triggered.connect(self.on_save_clicked)
        self.actionLoad_data_from_file.triggered.connect(self.on_load_clicked)
        self.actionSaveDefaults.triggered.connect(self.on_save_default)
        self.actionLoadDefaults.triggered.connect(self.on_load_default)

    # =============================================================================
    # Measurement callbacks
    # =============================================================================

    def apply_smu_settings(self):
        """
        Applies SMU settings to Keithley before a measurement.
        Warning: self.keithley.reset() will reset those settings.
        """
        for tab in self.smu_tabs:

            smu = getattr(self.keithley, tab.smu_name)

            if tab.sense_type.currentIndex() == tab.SENSE_LOCAL:
                smu.sense = smu.SENSE_LOCAL
            elif tab.sense_type.currentIndex() == tab.SENSE_REMOTE:
                smu.sense = smu.SENSE_REMOTE

            lim_i = tab.limit_i.value()
            smu.source.limiti = lim_i
            smu.trigger.source.limiti = lim_i

            lim_v = tab.limit_v.value()
            smu.source.limitv = lim_v
            smu.trigger.source.limitv = lim_v

            smu.source.highc = int(tab.high_c.isChecked())

    @QtCore.pyqtSlot()
    def on_sweep_clicked(self):
        """ Start a transfer measurement with current settings."""

        if self.keithley.busy:
            msg = "Keithley is currently busy. Please try again later."
            QtWidgets.QMessageBox.information(self, "Keithley Busy", msg)

            return

        self.apply_smu_settings()

        params = dict()

        if self.tabWidgetSweeps.currentIndex() == 0:
            self.statusBar.showMessage("    Recording transfer curve.")
            # get sweep settings
            params["sweep_type"] = "transfer"
            params["VgStart"] = self.transfer_sweep_settings.vg_start.value()
            params["VgStop"] = self.transfer_sweep_settings.vg_stop.value()
            params["VgStep"] = self.transfer_sweep_settings.vg_step.value()
            params["VdList"] = self.transfer_sweep_settings.vd_list.value()

        elif self.tabWidgetSweeps.currentIndex() == 1:
            self.statusBar.showMessage("    Recording output curve.")
            # get sweep settings
            params["sweep_type"] = "output"
            params["VdStart"] = self.output_sweep_settings.vd_start.value()
            params["VdStop"] = self.output_sweep_settings.vd_stop.value()
            params["VdStep"] = self.output_sweep_settings.vd_step.value()
            params["VgList"] = self.output_sweep_settings.vg_list.value()

        elif self.tabWidgetSweeps.currentIndex() == 2:
            self.statusBar.showMessage("    Recording IV curve.")
            # get sweep settings
            params["sweep_type"] = "iv"
            params["VStart"] = self.iv_sweep_settings.v_start.value()
            params["VStop"] = self.iv_sweep_settings.v_stop.value()
            params["VStep"] = self.iv_sweep_settings.v_step.value()
            smusweep = self.iv_sweep_settings.smu_sweep.currentText()
            params["smu_sweep"] = getattr(self.keithley, smusweep)

        else:
            return

        # get general sweep settings
        smu_gate = self.general_sweep_settings.smu_gate.currentText()
        smu_drain = self.general_sweep_settings.smu_drain.currentText()
        params["tInt"] = self.general_sweep_settings.t_int.value()
        params["delay"] = self.general_sweep_settings.t_settling.value()
        params["smu_gate"] = getattr(self.keithley, smu_gate)
        params["smu_drain"] = getattr(self.keithley, smu_drain)
        params["pulsed"] = bool(self.general_sweep_settings.sweep_type.currentIndex())

        # check if integration time is valid, return otherwise
        freq = self.keithley.localnode.linefreq

        if not 0.001 / freq < params["tInt"] < 25.0 / freq:
            msg = (
                "Integration time must be between 0.001 and 25 "
                + "power line cycles of 1/(%s Hz)." % freq
            )
            QtWidgets.QMessageBox.information(self, "Parameter Error", msg)

            return

        # create measurement thread with params dictionary
        self.measureThread = MeasureThread(self.keithley, params)
        self.measureThread.finished_sig.connect(self.on_measure_done)
        self.measureThread.error_sig.connect(self.on_measure_error)

        # run measurement
        self._gui_state_busy()
        self.measureThread.start()

    def on_measure_done(self, sd):
        self.statusBar.showMessage("    Ready.")
        self._gui_state_idle()
        self.actionSaveSweepData.setEnabled(True)

        self.sweep_data = sd
        self.canvas.plot(self.sweep_data)
        if not self.keithley.abort_event.is_set():
            self.on_save_clicked()

    def on_measure_error(self, exc):
        self.statusBar.showMessage("    Ready.")
        self._gui_state_idle()
        QtWidgets.QMessageBox.information(
            self, "Sweep Error", f"{exc.__class__.__name__}: {exc.args[0]}"
        )

    @QtCore.pyqtSlot()
    def on_abort_clicked(self):
        """
        Aborts current measurement.
        """
        self.keithley.abort_event.set()
        for smu in self.smu_list:
            getattr(self.keithley, smu).abort()
        self.keithley.reset()

    # =============================================================================
    # Interface callbacks
    # =============================================================================

    @QtCore.pyqtSlot()
    def on_connect_clicked(self):
        self.keithley.connect()
        self.update_smu_list()
        self.update_gui_connection()
        if not self.keithley.connected:
            msg = (
                f"Keithley cannot be reached at {self.keithley.visa_address}. "
                f"Please check if address is correct and Keithley is turned on."
            )
            QtWidgets.QMessageBox.information(self, "Connection Error", msg)

    @QtCore.pyqtSlot()
    def on_disconnect_clicked(self):
        self.keithley.disconnect()
        self.update_gui_connection()
        self.statusBar.showMessage("    No Keithley connected.")

    @QtCore.pyqtSlot()
    def on_save_clicked(self):
        """Show GUI to save current sweep data as text file."""
        prompt = "Save as .txt file."
        filename = "untitled.txt"
        formats = "Text file (*.txt)"
        filepath, _ = QtWidgets.QFileDialog.getSaveFileName(
            self, prompt, filename, formats
        )
        if len(filepath) < 4:
            return
        self.sweep_data.save(filepath)

    @QtCore.pyqtSlot()
    def on_load_clicked(self):
        """Show GUI to load sweep data from file."""
        prompt = "Please select a data file."
        filepath, _ = QtWidgets.QFileDialog.getOpenFileName(self, prompt)
        if not osp.isfile(filepath):
            return

        self.sweep_data = FETResultTable()
        self.sweep_data.load(filepath)

        self.canvas.plot(self.sweep_data)
        self.actionSaveSweepData.setEnabled(True)

    @QtCore.pyqtSlot()
    def on_save_default(self):
        """Saves current settings from GUI as defaults."""

        # save sweep settings
        self.transfer_sweep_settings.save_defaults()
        self.output_sweep_settings.save_defaults()
        self.iv_sweep_settings.save_defaults()
        self.general_sweep_settings.save_defaults()

        # save smu specific settings
        for tab in self.smu_tabs:
            tab.save_defaults()

    @QtCore.pyqtSlot()
    def on_load_default(self):
        """Load default settings to interface."""

        # load sweep settings
        self.transfer_sweep_settings.load_defaults()
        self.output_sweep_settings.load_defaults()
        self.iv_sweep_settings.load_defaults()
        self.general_sweep_settings.load_defaults()

        # smu settings
        for tab in self.smu_tabs:
            tab.load_defaults()

    @QtCore.pyqtSlot()
    def exit_(self):
        self.keithley.disconnect()
        self.connection_status_update.stop()
        self.save_geometry()
        self.deleteLater()

    # =============================================================================
    # Interface states
    # =============================================================================

    def update_gui_connection(self):
        """Check if Keithley is connected and update GUI."""
        if self.keithley.connected:

            try:
                test = self.keithley.localnode.model
            except (
                pyvisa.VisaIOError,
                pyvisa.InvalidSession,
                OSError,
                KeithleyIOError,
            ):
                self.keithley.disconnect()
                self._gui_state_disconnected()
            else:

                if self.keithley.busy:
                    self._gui_state_busy()
                else:
                    self._gui_state_idle()
        else:
            self._gui_state_disconnected()

    def _gui_state_busy(self):
        """Set GUI to state for running measurement."""

        self.pushButtonRun.setEnabled(False)
        self.pushButtonAbort.setEnabled(True)

        self.actionConnect.setEnabled(False)
        self.actionDisconnect.setEnabled(False)

        self.statusBar.showMessage("    Measuring.")
        self.led.setChecked(True)

    def _gui_state_idle(self):
        """Set GUI to state for IDLE Keithley."""

        self.pushButtonRun.setEnabled(True)
        self.pushButtonAbort.setEnabled(False)

        self.actionConnect.setEnabled(False)
        self.actionDisconnect.setEnabled(True)
        self.statusBar.showMessage("    Ready.")
        self.led.setChecked(True)

    def _gui_state_disconnected(self):
        """Set GUI to state for disconnected Keithley."""

        self.pushButtonRun.setEnabled(False)
        self.pushButtonAbort.setEnabled(False)

        self.actionConnect.setEnabled(True)
        self.actionDisconnect.setEnabled(False)
        self.statusBar.showMessage("    No Keithley connected.")
        self.led.setChecked(False)