예제 #1
0
 def parameters(self):
     parameterDict = super(RangeEvaluation, self).parameters()
     tooltipLookup = SequenceDict([ ('min', 'Range minimum (inclusive)'),
                                    ('max', 'Range maximum (inclusive)') ])
     for name, tooltip in tooltipLookup.items():
         parameterDict[name] = Parameter(name=name, dataType='magnitude', value=self.settings[name],
                                         text=self.settings.get( (name, 'text') ), tooltip=tooltip)
     parameterDict['invert'] = Parameter(name='invert', dataType='bool', value=self.settings['invert'])
     return parameterDict
예제 #2
0
 def parameters(self):
     parameterDict = super(TwoIonEvaluation, self).parameters()
     parameterDict['Ion_1'] = Parameter(name='Ion_1', dataType='str', value=self.settings['Ion_1'], tooltip='The evaluation for ion 1')
     parameterDict['Ion_2'] = Parameter(name='Ion_2', dataType='str', value=self.settings['Ion_2'], tooltip='The evaluation for ion 2')
     tooltipLookup = SequenceDict([ ('dd', 'multiplier for dd'),
                                    ('db', 'multiplier for db'),
                                    ('bd', 'multiplier for bd'),
                                    ('bb', 'multiplier for bb') ])
     for name, tooltip in tooltipLookup.items():
         parameterDict[name] = Parameter(name=name, dataType='magnitude', value=self.settings[name],
                                         text=self.settings.get( (name, 'text') ), tooltip=tooltip)
     return parameterDict
예제 #3
0
 def parameters(self):
     parameterDict = super(FeedbackEvaluation, self).parameters()
     if isinstance(self.settings['SetPoint'], ExpressionValue):
         self.settings['SetPoint'] = self.settings['SetPoint'].value
     tooltipLookup = SequenceDict([ ('SetPoint', 'Set point of PI loop'),
                                    ('P', 'Proportional gain'),
                                    ('I', 'Integral gain'),
                                    ('AveragingTime', 'Time spent accumulating data before updating the servo output') ])
     for name, tooltip in tooltipLookup.items():
         parameterDict[name] = Parameter(name=name, dataType='magnitude', value=self.settings[name],
                                         text=self.settings.get( (name, 'text') ), tooltip=tooltip)
     parameterDict['GlobalVariable'] = Parameter(name='GlobalVariable', dataType='select', value=self.settings['GlobalVariable'],
                                                 choices=sorted(self.globalDict.keys()), tooltip="Name of variable to which servo output value should be pushed")
     parameterDict['Reset'] = Parameter(name='Reset', dataType='bool', value=self.settings['Reset'], tooltip="Reset integrator")
     return parameterDict
class VoltageGlobalAdjust(VoltageGlobalAdjustForm, VoltageGlobalAdjustBase):
    updateOutput = QtCore.pyqtSignal(object, object)
    outputChannelsChanged = QtCore.pyqtSignal(object)
    _channelParams = {}

    def __init__(self, config, globalDict, parent=None):
        VoltageGlobalAdjustForm.__init__(self)
        VoltageGlobalAdjustBase.__init__(self, parent)
        self.config = config
        self.configname = 'VoltageGlobalAdjust.Settings'
        self.settings = self.config.get(self.configname, Settings())
        self.globalAdjustDict = SequenceDict()
        self.myLabelList = list()
        self.myBoxList = list()
        self.historyCategory = 'VoltageGlobalAdjust'
        self.adjustHistoryName = None
        self.globalDict = globalDict
        self.adjustCache = self.config.get(self.configname + ".cache", dict())
        self.savedValue = defaultdict(lambda: None)
        self.displayValueObservable = defaultdict(lambda: Observable())

    def setupUi(self, parent):
        VoltageGlobalAdjustForm.setupUi(self, parent)
        self.gainBox.setValue(self.settings.gain)
        self.gainBox.valueChanged.connect(self.onGainChanged)
        self.tableModel = VoltageGlobalAdjustTableModel(
            self.globalAdjustDict, self.globalDict)
        self.tableView.setModel(self.tableModel)
        self.tableView.setSortingEnabled(True)  # triggers sorting
        self.delegate = MagnitudeSpinBoxDelegate(self.globalDict)
        self.tableView.setItemDelegateForColumn(1, self.delegate)

    def onGainChanged(self, gain):
        self.settings.gain = gain
        self.updateOutput.emit(self.globalAdjustDict, self.settings.gain)

    def setupGlobalAdjust(self, name, adjustDict):
        if name != self.adjustHistoryName:
            self.adjustCache[self.adjustHistoryName] = [
                v.data for v in list(self.globalAdjustDict.values())
            ]
            self.settings.gainCache[
                self.adjustHistoryName] = self.settings.gain
            self.settings.gain = self.settings.gainCache.get(
                name, self.settings.gain)
            if name in self.adjustCache:
                for data in self.adjustCache[name]:
                    if data[0] in adjustDict:
                        adjustDict[data[0]].data = data
            self.adjustHistoryName = name
        self.globalAdjustDict = adjustDict
        for name, adjust in self.globalAdjustDict.items():
            try:
                adjust.valueChanged.connect(self.onValueChanged,
                                            QtCore.Qt.UniqueConnection)
            except:
                pass
        self.tableModel.setGlobalAdjust(adjustDict)
        self.outputChannelsChanged.emit(self.outputChannels())
        self.gainBox.setValue(self.settings.gain)
        #self.updateOutput.emit(self.globalAdjustDict, self.settings.gain)

    def onValueChanged(self, name, value, string, origin):
        if origin == 'recalculate':
            self.tableModel.valueRecalcualted(name)
        self.globalAdjustDict[name]._value = float(
            self.globalAdjustDict[name]._value)
        self.updateOutput.emit(self.globalAdjustDict, self.settings.gain)

    def saveConfig(self):
        self.config[self.configname] = self.settings
        self.adjustCache[self.adjustHistoryName] = [
            v.data for v in list(self.globalAdjustDict.values())
        ]
        self.config[self.configname + ".cache"] = self.adjustCache

    def setValue(self, channel, value):
        self.globalAdjustDict[channel].value = value
        return value

    def getValue(self, channel):
        return self.globalAdjustDict[channel].value

    def currentValue(self, channel):
        return self.globalAdjustDict[channel].value

    def saveValue(self, channel):
        self.savedValue[channel] = self.globalAdjustDict[channel].value

    def restoreValue(self, channel):
        if self.savedValue[channel] is not None:
            self.globalAdjustDict[channel].value = self.savedValue[channel]
        return True

    def strValue(self, channel):
        adjust = self.globalAdjustDict[channel]
        return adjust.string if adjust.hasDependency else None

    def setStrValue(self, channel, value):
        pass

    def outputChannels(self):
        self._outputChannels = dict(
            ((channelName,
              VoltageOutputChannel(
                  self,
                  None,
                  channelName,
                  self.globalDict,
              )) for channelName in self.globalAdjustDict.keys()))
        return self._outputChannels
예제 #5
0
class ExperimentUi(WidgetContainerBase,WidgetContainerForm):
    levelNameList = ["debug", "info", "warning", "error", "critical"]
    levelValueList = [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL]

    def __init__(self, config, project):
        self.config = config
        self.project = project
        super(ExperimentUi, self).__init__()
        self.settings = FPGASettings()
        self.loggingLevel = config.get('Settings.loggingLevel',logging.INFO)
        self.consoleMaximumLines = config.get('Settings.consoleMaximumLinesNew',100)
        self.consoleEnable = config.get('Settings.consoleEnable',False)
        self.shutterNameDict = config.get('Settings.ShutterNameDict', ChannelNameDict())
        if self.shutterNameDict.__class__.__name__ == 'ChannelNameMap':
            self.shutterNameDict = ChannelNameDict( self.shutterNameDict.names )
        if self.shutterNameDict.customDict.__class__.__name__ == 'ChannelNameMap':
            self.shutterNameDict = ChannelNameDict(self.shutterNameDict.customDict._fwd, self.shutterNameDict.defaultDict )

        self.shutterNameSignal = DataChanged()
        self.triggerNameDict = config.get('Settings.TriggerNameDict', ChannelNameDict())
        if self.triggerNameDict.__class__.__name__ == 'ChannelNameMap':
            self.triggerNameDict = ChannelNameDict( self.triggerNameDict.names )
        self.triggerNameSignal = DataChanged()
        if self.loggingLevel not in self.levelValueList: self.loggingLevel = logging.INFO
        self.dbConnection = project.dbConnection
        self.objectListToSaveContext = list()
        self.voltageControlWindow = None

        localpath = getProject().configDir+'/UserFunctions/'
        for filename in Path(localpath.replace('\\','/')).glob('**/*.py'):
            try:
                importlib.machinery.SourceFileLoader("CustomFunctions", str(filename).replace('\\','/')).load_module()
            except SyntaxError as e:
                SyntaxError('Failed to load {0}'.format(str(filename)))

    def __enter__(self):
        self.pulser = PulserHardware()
        return self
    
    def __exit__(self, excepttype, value, traceback):
        self.pulser.shutdown()
        return False
    
    def setupUi(self, parent):
        super(ExperimentUi,self).setupUi(parent)
        self.dockWidgetConsole.hide()
        self.loggerUi = LoggerLevelsUi(self.config)
        self.loggerUi.setupUi(self.loggerUi)
        self.loggerDock = QtWidgets.QDockWidget("Logging")
        self.loggerDock.setWidget(self.loggerUi)
        self.loggerDock.setObjectName("_LoggerDock")
        self.addDockWidget( QtCore.Qt.RightDockWidgetArea, self.loggerDock)
        self.loggerDock.hide()

        logger = logging.getLogger()
        self.exceptionToolBar.addWidget(ExceptionLogButton())

        self.warningLogButton = LogButton(messageIcon=":/petersIcons/icons/Warning.png", messageName="warnings")
        self.exceptionToolBar.addWidget(self.warningLogButton)
        qtWarningButtonHandler.textWritten.connect(self.warningLogButton.addMessage)

        # Setup Console Dockwidget
        self.levelComboBox.addItems(self.levelNameList)
        self.levelComboBox.currentIndexChanged[int].connect( self.setLoggingLevel )
        self.levelComboBox.setCurrentIndex( self.levelValueList.index(self.loggingLevel) )
        self.consoleClearButton.clicked.connect( self.onClearConsole )
        self.linesSpinBox.valueChanged.connect( self.onConsoleMaximumLinesChanged )
        self.linesSpinBox.setValue( self.consoleMaximumLines )
        self.checkBoxEnableConsole.stateChanged.connect( self.onEnableConsole )
        self.checkBoxEnableConsole.setChecked( self.consoleEnable )

        self.parent = parent
        self.tabDict = SequenceDict()

        if self.project.isEnabled('software', 'Memory Profiler'):
            self.memoryProfiler = MemoryProfiler(self)

        #determine if Voltages software is enabled and import class if it is
        self.voltagesEnabled = self.project.isEnabled('software', 'Voltages')
        if self.voltagesEnabled:
            from voltageControl.VoltageControl import VoltageControl

        #setup external parameters; import specific libraries if they are needed, popup warnings if selected hardware import fail
        import externalParameter.StandardExternalParameter
        import externalParameter.InterProcessParameters
        if self.project.isEnabled('hardware', 'Conex Motion'):
            try:
                import externalParameter.MotionParameter #@UnusedImport
            except ImportError: #popup on failed import
                importErrorPopup('Conex Motion')
        if self.project.isEnabled('hardware', 'APT Motion'):
            try:
                import externalParameter.APTInstruments  # @UnusedImport
                externalParameter.APTInstruments.loadDll(
                    list(self.project.hardware['APT Motion'].values())[0]['dllPath'])
            except Exception as e:  # popup on failed import
                importErrorPopup('APT Motion error {0}'.format(e))
        if self.project.isEnabled('hardware', 'Lab Brick'):
            try:
                import externalParameter.LabBrick  # @UnusedImport
                externalParameter.LabBrick.loadDll(
                    list(self.project.hardware['Lab Brick'].values())[0]['dllPath'])
            except Exception as e:  # popup on failed import
                importErrorPopup('Lab Brick error {0}'.format(e))
        from externalParameter.ExternalParameterBase import InstrumentDict

        # setup FPGAs
        self.setupFPGAs()

        # initialize PulseProgramUi
        pulserConfig = self.pulser.pulserConfiguration()
        self.shutterNameDict.defaultDict = pulserConfig.shutterBits if pulserConfig else dict()
        self.triggerNameDict.defaultDict = pulserConfig.triggerBits if pulserConfig else dict()
        self.counterNameDict = pulserConfig.counterBits if pulserConfig else dict()
        self.channelNameData = (self.shutterNameDict, self.shutterNameSignal, self.triggerNameDict, self.triggerNameSignal, self.counterNameDict )
        self.pulseProgramDialog = PulseProgramUi.PulseProgramSetUi(self.config,  self.channelNameData )
        self.pulseProgramDialog.setupUi(self.pulseProgramDialog)

        # Global Variables
        self.globalVariablesUi = GlobalVariablesUi(self.config)
        self.globalVariablesUi.setupUi(self.globalVariablesUi)
        self.globalVariablesDock = QtWidgets.QDockWidget("Global Variables")
        self.globalVariablesDock.setObjectName("Global Variables")
        self.globalVariablesDock.setWidget( self.globalVariablesUi )
        self.addDockWidget(QtCore.Qt.RightDockWidgetArea , self.globalVariablesDock)

        self.measurementLog = MeasurementLogUi(self.config, self.dbConnection)
        self.measurementLog.setupUi(self.measurementLog)
        #self.measurementLogDock = QtWidgets.QDockWidget("Measurement Log")
        #self.measurementLogDock.setWidget( self.measurementLog )
        #self.measurementLogDock.setObjectName('_MeasurementLog')
        #self.addDockWidget( QtCore.Qt.BottomDockWidgetArea, self.measurementLogDock )

        self.preferencesUi = PreferencesUi(config, self)
        self.preferencesUi.setupUi(self.preferencesUi)
        self.preferencesUiDock = QtGui.QDockWidget("Print Preferences")
        self.preferencesUiDock.setWidget(self.preferencesUi)
        self.preferencesUiDock.setObjectName("_preferencesUi")
        self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.preferencesUiDock)

        for widget, name in [(ScanExperiment.ScanExperiment(self.settings, self.pulser, self.globalVariablesUi,
                                                            "ScanExperiment", toolBar=self.experimentToolBar,
                                                            measurementLog=self.measurementLog,
                                                            callWhenDoneAdjusting=self.callWhenDoneAdjusting,
                                                            preferences=self.preferencesUi.preferences().printPreferences),
                              "Scan")
                             ]:
            widget.setupUi(widget, self.config)
            if hasattr(widget, 'setPulseProgramUi'):
                widget.setPulseProgramUi(self.pulseProgramDialog)
            if hasattr(widget, 'plotsChanged'):
                widget.plotsChanged.connect(self.initMenu)
            self.tabWidget.addTab(widget, name)
            self.tabDict[name] = widget
            widget.ClearStatusMessage.connect(self.statusbar.clearMessage)
            widget.StatusMessage.connect(self.statusbar.showMessage)
            widget.stashChanged.connect(self.onStashChanged)

        self.scanExperiment = self.tabDict["Scan"]

        self.shutterUi, self.shutterDockWidget = self.instantiateShutterUi(self.pulser, 'Shutters', "ShutterUi", self.config, self.globalVariablesUi.globalDict, self.shutterNameDict, self.shutterNameSignal)

        self.triggerUi = ShutterUi.TriggerUi(self.pulser, 'ShutterUi', 'trigger', self.config, (self.triggerNameDict, self.triggerNameSignal) )
        self.triggerUi.offColor =  QtGui.QColor(QtCore.Qt.white)
        self.triggerUi.setupUi(self.triggerUi)
        self.pulser.ppActiveChanged.connect( self.triggerUi.setDisabled )
        self.triggerDockWidget.setWidget( self.triggerUi )

        #AWGs
        enabledAWGDict = {displayName:className for displayName,className in AWGDevices.AWGDeviceDict.items()
                          if self.project.isEnabled('hardware', displayName)}
        self.AWGUiDict = dict()
        if enabledAWGDict:
            AWGIcon = QtGui.QIcon()
            AWGPixmap = QtGui.QPixmap(":/other/icons/AWG.png")
            AWGIcon.addPixmap(AWGPixmap)
            AWGButton = QtWidgets.QToolButton()
            AWGButton.setIcon(AWGIcon)
            self.toolBar.addWidget(AWGButton)
            if len(enabledAWGDict) > 1:
                menu = QtWidgets.QMenu("AWG")
                AWGButton.setMenu(menu)
                AWGButton.setPopupMode(QtWidgets.QToolButton.InstantPopup)
                menu.setIcon(AWGIcon)
                self.menuWindows.addMenu(menu)
            for displayName, className in enabledAWGDict.items():
                awgUi = AWGUi(getattr(AWGDevices, className), self.config, self.globalVariablesUi.globalDict, self.scanExperiment.pulseProgramUi)
                self.AWGUiDict[displayName] = awgUi
                awgUi.setupUi(awgUi)
                awgUi.varDictChanged.connect( partial(self.scanExperiment.updateScanTarget, displayName) )
                self.scanExperiment.updateScanTarget( displayName, awgUi.varAsOutputChannelDict )
                self.globalVariablesUi.valueChanged.connect( awgUi.evaluate )
                action = QtWidgets.QAction(AWGIcon, displayName, self)
                action.triggered.connect(partial(self.onAWG, displayName))
                if len(enabledAWGDict) > 1:
                    menu.addAction(action)
                else:
                    self.menuWindows.addAction(action)
                    AWGButton.clicked.connect(action.trigger)

        ParameterUi, self.pulserParameterUiDock = self.instantiateParametersUi(self.pulser, "Pulser Parameters", "PulserParameterUi", self.config, self.globalVariablesUi.globalDict)
        self.objectListToSaveContext.append(ParameterUi)

        self.DDSUi, self.DDSDockWidget = self.instantiateDDSUi(self.pulser, "DDS", "DDSUi", self.config, self.globalVariablesUi.globalDict)
        self.objectListToSaveContext.append(self.DDSUi)

        self.DACUi, self.DACDockWidget = self.instantiateDACUi(self.pulser, "DAC", "dacUi", self.config, self.globalVariablesUi.globalDict)
        self.objectListToSaveContext.append(self.DACUi)

#         self.DDSUi9910 = DDSUi9910.DDSUi(self.config, self.pulser )
#         self.DDSUi9910.setupUi(self.DDSUi9910)
#         self.DDS9910DockWidget.setWidget( self.DDSUi9910 )
#        self.pulser.ppActiveChanged.connect( self.DDSUi9910.setDisabled )
        #self.tabDict['Scan'].NeedsDDSRewrite.connect( self.DDSUi9910.onWriteAll )
        self.instantiateAuxiliaryPulsers()

        self.valueHistoryUi = ValueHistoryUi(self.config, self.dbConnection)
        self.valueHistoryUi.setupUi( self.valueHistoryUi )
        self.valueHistoryDock = QtWidgets.QDockWidget("Value History")
        self.valueHistoryDock.setWidget( self.valueHistoryUi )
        self.valueHistoryDock.setObjectName("_valueHistory")
        self.addDockWidget( QtCore.Qt.RightDockWidgetArea, self.valueHistoryDock )
        
        # tabify the dock widgets
        self.tabifyDockWidget( self.pulserParameterUiDock, self.preferencesUiDock)
        self.tabifyDockWidget( self.preferencesUiDock, self.triggerDockWidget )
        self.tabifyDockWidget( self.triggerDockWidget, self.shutterDockWidget)
        self.tabifyDockWidget( self.shutterDockWidget, self.DDSDockWidget )
        self.tabifyDockWidget( self.DDSDockWidget, self.DACDockWidget )
#        self.tabifyDockWidget( self.DDSDockWidget, self.DDS9910DockWidget )
#        self.tabifyDockWidget( self.DDS9910DockWidget, self.globalVariablesDock )
        self.tabifyDockWidget( self.DACDockWidget, self.globalVariablesDock )
        self.tabifyDockWidget( self.globalVariablesDock, self.valueHistoryDock )
        self.triggerDockWidget.hide()
        self.preferencesUiDock.hide()

        self.ExternalParametersSelectionUi = ExternalParameterSelection.SelectionUi(self.config, self.globalVariablesUi.globalDict, classdict=InstrumentDict)
        self.ExternalParametersSelectionUi.setupUi( self.ExternalParametersSelectionUi )
        self.ExternalParameterSelectionDock = QtWidgets.QDockWidget("Params Selection")
        self.ExternalParameterSelectionDock.setObjectName("_ExternalParameterSelectionDock")
        self.ExternalParameterSelectionDock.setWidget(self.ExternalParametersSelectionUi)
        self.addDockWidget( QtCore.Qt.RightDockWidgetArea, self.ExternalParameterSelectionDock)

        self.ExternalParametersUi = ExternalParameterUi.ControlUi(self.config, self.globalVariablesUi.globalDict)
        self.ExternalParametersUi.setupUi(self.ExternalParametersSelectionUi.outputChannels())

        self.ExternalParameterDock = QtWidgets.QDockWidget("Params Control")
        self.ExternalParameterDock.setWidget(self.ExternalParametersUi)
        self.ExternalParameterDock.setObjectName("_ExternalParameterDock")
        self.addDockWidget( QtCore.Qt.RightDockWidgetArea, self.ExternalParameterDock)
        self.ExternalParametersSelectionUi.outputChannelsChanged.connect( self.ExternalParametersUi.setupParameters )

        self.instrumentLoggingDisplay = InstrumentLoggingDisplay(self.config)
        self.instrumentLoggingDisplay.setupUi( self.ExternalParametersSelectionUi.inputChannels(), self.instrumentLoggingDisplay )
        self.instrumentLoggingDisplayDock = QtWidgets.QDockWidget("Params Reading")
        self.instrumentLoggingDisplayDock.setObjectName("_ExternalParameterDisplayDock")
        self.instrumentLoggingDisplayDock.setWidget(self.instrumentLoggingDisplay)
        self.addDockWidget( QtCore.Qt.RightDockWidgetArea, self.instrumentLoggingDisplayDock)
        self.ExternalParametersSelectionUi.inputChannelsChanged.connect( self.instrumentLoggingDisplay.setupParameters )
               
        self.ExternalParametersSelectionUi.outputChannelsChanged.connect( partial(self.scanExperiment.updateScanTarget, 'External') )               
        self.scanExperiment.updateScanTarget( 'External', self.ExternalParametersSelectionUi.outputChannels() )
        
        self.todoList = TodoList( self.tabDict, self.config, self.getCurrentTab, self.switchTab, self.globalVariablesUi )
        self.todoList.setupUi()
        self.todoListDock = QtWidgets.QDockWidget("Todo List")
        self.todoListDock.setWidget(self.todoList)
        self.todoListDock.setObjectName("_todoList")
        self.addDockWidget( QtCore.Qt.RightDockWidgetArea, self.todoListDock)
        self.tabifyDockWidget(self.valueHistoryDock, self.todoListDock)

        for name, widget in self.tabDict.items():
            if hasattr( widget, 'scanConfigurationListChanged' ) and widget.scanConfigurationListChanged is not None:
                widget.scanConfigurationListChanged.connect( partial( self.todoList.populateMeasurementsItem, name)  )
            if hasattr( widget, 'evaluationConfigurationChanged' ) and widget.evaluationConfigurationChanged is not None:
                widget.evaluationConfigurationChanged.connect( partial( self.todoList.populateEvaluationItem, name)  )
            if hasattr( widget, 'analysisConfigurationChanged' ) and widget.analysisConfigurationChanged is not None:
                widget.analysisConfigurationChanged.connect( partial( self.todoList.populateAnalysisItem, name)  )
       
        #tabify external parameters controls
        self.tabifyDockWidget(self.ExternalParameterSelectionDock, self.ExternalParameterDock)
        self.tabifyDockWidget(self.ExternalParameterDock, self.instrumentLoggingDisplayDock)
        
        self.tabWidget.currentChanged.connect(self.onCurrentChanged)
        self.actionClear.triggered.connect(self.onClear)
        self.actionPause.triggered.connect(self.onPause)

        #Save and load actions
        self.actionSave_GUI.triggered.connect(self.onSaveGUI)
        self.actionSave_GUI_Yaml.triggered.connect(self.onSaveGUIYaml)

        self.actionStart.triggered.connect(self.onStart)
        self.actionStop.triggered.connect(self.onStop)
        self.actionAbort.triggered.connect(self.onAbort)
        self.actionExit.triggered.connect(self.onClose)
        self.actionContinue.triggered.connect(self.onContinue)
        self.actionPulses.triggered.connect(self.onPulses)
        self.actionReload.triggered.connect(self.onReload)
        self.actionProject.triggered.connect( self.onProjectSelection)
        self.actionDocumentation.triggered.connect(self.onShowDocumentation)
        if self.voltagesEnabled:
            self.actionVoltageControl.triggered.connect(self.onVoltageControl)
        else:
            self.actionVoltageControl.setDisabled(True)
            self.actionVoltageControl.setVisible(False)
        self.actionScripting.triggered.connect(self.onScripting)
        self.actionUserFunctions.triggered.connect(self.onUserFunctionsEditor)
        self.actionMeasurementLog.triggered.connect(self.onMeasurementLog)
        self.actionDedicatedCounters.triggered.connect(self.showDedicatedCounters)
        self.actionLogic.triggered.connect(self.showLogicAnalyzer)
        self.currentTab = self.tabDict.at( min(len(self.tabDict)-1, self.config.get('MainWindow.currentIndex',0) ) )
        self.tabWidget.setCurrentIndex( self.config.get('MainWindow.currentIndex',0) )
        self.currentTab.activate()
        if hasattr( self.currentTab, 'stateChanged' ):
            self.currentTab.stateChanged.connect( self.todoList.onStateChanged )
        if 'MainWindow.State' in self.config:
            self.parent.restoreState(self.config['MainWindow.State'])
        self.initMenu()
        self.actionResume.setEnabled(False)
        if 'MainWindow.pos' in self.config:
            self.move(self.config['MainWindow.pos'])
        if 'MainWindow.size' in self.config:
            self.resize(self.config['MainWindow.size'])
        if 'MainWindow.isMaximized' in self.config:
            if self.config['MainWindow.isMaximized']:
                self.showMaximized()
        else:
            self.showMaximized()
            
        self.dedicatedCountersWindow = DedicatedCounters(self.config, self.dbConnection, self.pulser, self.globalVariablesUi, self.shutterUi,self.ExternalParametersUi.callWhenDoneAdjusting )
        self.dedicatedCountersWindow.setupUi(self.dedicatedCountersWindow)
        
        self.logicAnalyzerWindow = LogicAnalyzer(self.config, self.pulser, self.channelNameData )
        self.logicAnalyzerWindow.setupUi(self.logicAnalyzerWindow)

        if self.voltagesEnabled:
            try:
                self.voltageControlWindow = VoltageControl(self.config, self.globalVariablesUi.globalDict, self.dac)
                self.voltageControlWindow.setupUi(self.voltageControlWindow)
                self.voltageControlWindow.globalAdjustUi.outputChannelsChanged.connect( partial(self.scanExperiment.updateScanTarget, 'Voltage') )
                self.voltageControlWindow.localAdjustUi.outputChannelsChanged.connect( partial(self.scanExperiment.updateScanTarget, 'Voltage Local Adjust') )
                self.scanExperiment.updateScanTarget('Voltage', self.voltageControlWindow.globalAdjustUi.outputChannels())
                self.scanExperiment.updateScanTarget('Voltage Local Adjust', self.voltageControlWindow.localAdjustUi.outputChannels())
            except MyException.MissingFile as e:
                self.voltageControlWindow = None
                self.actionVoltageControl.setDisabled( True )
                logger.warning("Missing file - voltage subsystem disabled: {0}".format(str(e)))
            if self.voltageControlWindow:
                self.tabDict["Scan"].ppStartSignal.connect( self.voltageControlWindow.synchronize )   # upload shuttling data before running pule program
                self.dedicatedCountersWindow.autoLoad.setVoltageControl( self.voltageControlWindow )

        self.setWindowTitle("Experimental Control ({0})".format(self.project) )

        
        QtCore.QTimer.singleShot(60000, self.onCommitConfig )
        traceFilename, _ = DataDirectory.DataDirectory().sequencefile("Trace.log")
        LoggingSetup.setTraceFilename( traceFilename )
        errorFilename, _ = DataDirectory.DataDirectory().sequencefile("Error.log")
        LoggingSetup.setErrorFilename( errorFilename )
        
        # connect signals and slots for todolist and auto resume
        for name, widget in self.tabDict.items():
            if hasattr(widget,'onContinue'):
                self.dedicatedCountersWindow.autoLoad.ionReappeared.connect( widget.onContinue )
                
        # add PushDestinations
        for widget in self.tabDict.values():
            if hasattr(widget, 'addPushDestination'):
                widget.addPushDestination( 'External', self.ExternalParametersUi )
                
        # initialize ScriptingUi
        self.scriptingWindow = ScriptingUi(self)
        self.scriptingWindow.setupUi(self.scriptingWindow)

        # this is redundant in __init__ but this resolves issues with user-defined functions that reference NamedTraces
        localpath = getProject().configDir+'/UserFunctions/'
        for filename in Path(localpath.replace('\\','/')).glob('**/*.py'):
            try:
                importlib.machinery.SourceFileLoader("CustomFunctions", str(filename).replace('\\','/')).load_module()
            except SyntaxError as e:
                SyntaxError('Failed to load {0}'.format(str(filename)))

        # initialize NamedTraceUi
        self.userFunctionsEditor = UserFunctionsEditor(self, self.globalVariablesUi.globalDict)
        self.userFunctionsEditor.setupUi(self.userFunctionsEditor)

        # initialize StashButton
        self.actionStash.triggered.connect(self.onStash)
        self.actionResume.triggered.connect(self.onResume)
        self.stashButton = StashButtonControl(self.actionResume)
        self.stashButton.resume.connect(self.onResume)

    def instantiateParametersUi(self, pulser, windowName, configName, config, globalDict):
        ui = PulserParameterUi(pulser, config, configName, globalDict)
        ui.setupUi()
        uiDock = QtWidgets.QDockWidget(windowName)
        uiDock.setWidget(ui)
        uiDock.setObjectName(windowName)
        self.addDockWidget(QtCore.Qt.RightDockWidgetArea, uiDock)
        self.tabDict['Scan'].NeedsDDSRewrite.connect(ui.onWriteAll)
        return ui, uiDock

    def instantiateDDSUi(self, pulser, windowName, configName, config, globalDict):
        ui = DDSUi.DDSUi(pulser, config, configName, globalDict)
        ui.setupUi(ui)
        uiDock = QtWidgets.QDockWidget(windowName)
        uiDock.setWidget(ui)
        uiDock.setObjectName(windowName)
        self.addDockWidget(QtCore.Qt.RightDockWidgetArea, uiDock)
        self.globalVariablesUi.valueChanged.connect(ui.evaluate)
        pulser.ppActiveChanged.connect(ui.setDisabled)
        self.tabDict['Scan'].NeedsDDSRewrite.connect(ui.onWriteAll)
        return ui, uiDock

    def instantiateDACUi(self, pulser, windowName, configName, config, globalDict):
        ui = DACUi(pulser, config, configName, globalDict)
        ui.setupUi(ui)
        uiDock = QtWidgets.QDockWidget(windowName)
        uiDock.setObjectName(windowName)
        uiDock.setWidget(ui)
        self.addDockWidget(QtCore.Qt.RightDockWidgetArea, uiDock)
        pulser.ppActiveChanged.connect(ui.setDisabled)
        self.tabDict['Scan'].NeedsDDSRewrite.connect(ui.onWriteAll)
        return ui, uiDock

    def instantiateShutterUi(self, pulser, windowName, configName, config, globalDict, nameDict, nameSignal):
        ui = ShutterUi.ShutterUi(pulser, configName, 'shutter', self.config, (nameDict, nameSignal), size=49)
        ui.setupUi(ui, True)
        uiDock = QtWidgets.QDockWidget(windowName)
        uiDock.setObjectName(windowName)
        uiDock.setWidget(ui)
        self.addDockWidget(QtCore.Qt.RightDockWidgetArea, uiDock)
        pulser.ppActiveChanged.connect(ui.setDisabled)
        logger.debug("ShutterUi representation:" + repr(ui))
        return ui, uiDock

    def callWhenDoneAdjusting(self, callback):
        self.ExternalParametersUi.callWhenDoneAdjusting(callback)

    def onEnableConsole(self, state):
        self.consoleEnable = state==QtCore.Qt.Checked

    def onClearConsole(self):
        self.textEditConsole.clear()
        
    def onConsoleMaximumLinesChanged(self, maxlines):
        self.consoleMaximumLines = maxlines
        self.textEditConsole.document().setMaximumBlockCount(maxlines)
        
    def setLoggingLevel(self, index):
        self.loggingLevel = self.levelValueList[index]

    def showDedicatedCounters(self):
        self.dedicatedCountersWindow.show()
        self.dedicatedCountersWindow.setWindowState(QtCore.Qt.WindowActive)
        self.dedicatedCountersWindow.raise_()
        self.dedicatedCountersWindow.onStart() #Start displaying data immediately

    def showLogicAnalyzer(self):
        self.logicAnalyzerWindow.show()
        self.logicAnalyzerWindow.setWindowState(QtCore.Qt.WindowActive)
        self.logicAnalyzerWindow.raise_()

    def onVoltageControl(self):
        self.voltageControlWindow.show()
        self.voltageControlWindow.setWindowState(QtCore.Qt.WindowActive)
        self.voltageControlWindow.raise_()

    def onScripting(self):
        self.scriptingWindow.show()
        self.scriptingWindow.setWindowState(QtCore.Qt.WindowActive)
        self.scriptingWindow.raise_()

    def onUserFunctionsEditor(self):
        self.userFunctionsEditor.show()
        self.userFunctionsEditor.setWindowState(QtCore.Qt.WindowActive)
        self.userFunctionsEditor.raise_()

    def onAWG(self, displayName):
        awgUi = self.AWGUiDict[displayName]
        awgUi.show()
        awgUi.setWindowState(QtCore.Qt.WindowActive)
        awgUi.raise_()

    def onMeasurementLog(self):
        self.measurementLog.show()
        self.measurementLog.setWindowState(QtCore.Qt.WindowActive)
        self.measurementLog.raise_()
        
    def onClear(self):
        self.currentTab.onClear()
    
    def onSaveGUI(self, _):
        logger = logging.getLogger(__name__)
        self.currentTab.onSave()
        filename, _ = DataDirectory.DataDirectory().sequencefile("configuration.db")
        logger.info( "Saving config to "+filename )
        self.saveConfig()
        self.config.saveConfig(filename)

    def onSaveGUIYaml(self, _):
        self.currentTab.onSave()
        logger.info("Saving config")
        yamlfilename, _ = DataDirectory.DataDirectory().sequencefile("configuration.yaml")
        self.saveConfig()
        self.config.saveConfig(yamlfile=yamlfilename)

    def onCommitConfig(self):
        logger = logging.getLogger(__name__)
        self.currentTab.onSave()
        logger.debug( "Committing config" )
        self.saveConfig()
        self.config.commitToDatabase()
        QtCore.QTimer.singleShot(60000, self.onCommitConfig )      
            
    def onStart(self, checked=False, globalOverrides=list()):
        self.currentTab.onStart(globalOverrides)

    def onStash(self):
        if hasattr(self.currentTab, 'onStash'):
            self.currentTab.onStash()

    def onStashChanged(self, stash):
        self.actionResume.setEnabled(len(stash)>0)
        self.stashButton.onStashChanged(stash)

    def onResume(self, index=-1):
        if hasattr(self.currentTab, 'onResume'):
            self.currentTab.onResume(index)

    def onPause(self):
        self.currentTab.onPause()
    
    def onStop(self):
        self.currentTab.onStop()
        
    def onAbort(self):
        self.currentTab.onStop(reason='aborted')
        
    def onContinue(self):
        if hasattr(self.currentTab,'onContinue'):
            self.currentTab.onStop()
        else:
            self.statusbar.showMessage("continue not implemented")    
            
    def onReload(self):
        logger = logging.getLogger(__name__)
        logger.debug( "OnReload" )
        self.currentTab.onReload()
    
    def switchTab(self, name):
        self.tabWidget.setCurrentWidget( self.tabDict[name] )
        self.onCurrentChanged(self.tabDict.index(name))  # this gets called later, but we need it to run now in order to switch scans from the todolist
    
    def onCurrentChanged(self, index):
        if self.tabDict.at(index)!=self.currentTab:
            self.currentTab.deactivate()
            if hasattr( self.currentTab, 'stateChanged' ):
                try:
                    self.currentTab.stateChanged.disconnect()
                except TypeError:
                    pass
            self.currentTab = self.tabDict.at(index)
            self.currentTab.activate()
            if hasattr( self.currentTab, 'stateChanged' ):
                self.currentTab.stateChanged.connect( self.todoList.onStateChanged )
            self.initMenu()
            self.actionResume.setEnabled(self.currentTab.stashSize())
        
    def initMenu(self):
        """setup print and view menus"""
        #view menu
        self.menuView.clear()
        if hasattr(self.currentTab,'viewActions'):
            self.menuView.addActions(self.currentTab.viewActions())
        dockList = self.findChildren(QtWidgets.QDockWidget)
        for dock in dockList:
            self.menuView.addAction(dock.toggleViewAction())

        #print menu
        self.menuPrint.clear()
        if hasattr(self.currentTab,'printTargets'):
            for plot in self.currentTab.printTargets():
                action = self.menuPrint.addAction( plot )
                action.triggered.connect( partial(self.onPrint, plot ))
        self.menuPrint.addSeparator()
        action = self.menuPrint.addAction("Print Preferences")
        action.triggered.connect(self.preferencesUiDock.show)
        action.triggered.connect(self.preferencesUiDock.raise_)

    def onPulses(self):
        self.pulseProgramDialog.show()
        self.pulseProgramDialog.setWindowState(QtCore.Qt.WindowActive)
        self.pulseProgramDialog.raise_()
        if hasattr(self.currentTab,'experimentName'):
            self.pulseProgramDialog.setCurrentTab(self.currentTab.experimentName)
                  
    def onClose(self):
        self.parent.close()
        
    def onMessageWrite(self,message,level=logging.DEBUG):
        if self.consoleEnable and level>= self.loggingLevel:
            cursor = self.textEditConsole.textCursor()
            cursor.movePosition(QtGui.QTextCursor.End)
            if level < logging.ERROR:
                self.textEditConsole.setTextColor(QtCore.Qt.black)
            else:
                self.textEditConsole.setTextColor(QtCore.Qt.red)
            cursor.insertText(message)
            self.textEditConsole.setTextCursor(cursor)
            self.textEditConsole.ensureCursorVisible()
        
    def closeEvent(self,e):
        logger = logging.getLogger("")
        logger.debug( "Saving Configuration" )
        self.saveConfig()
        self.config.commitToDatabase()
        for tab in self.tabDict.values():
            tab.onClose()
        self.currentTab.deactivate()
        self.pulseProgramDialog.done(0)
        self.ExternalParametersSelectionUi.onClose()
        self.dedicatedCountersWindow.close()
        self.pulseProgramDialog.onClose()
        self.scriptingWindow.onClose()
        self.userFunctionsEditor.onClose()
        self.logicAnalyzerWindow.close()
        self.measurementLog.close()
        if self.voltagesEnabled:
            self.voltageControlWindow.close()
        for awgUi in self.AWGUiDict.values():
            awgUi.close()
        numTempAreas = len(self.scanExperiment.area.tempAreas)
        for i in range(numTempAreas):
            if len(self.scanExperiment.area.tempAreas) > 0:
                self.scanExperiment.area.tempAreas[0].win.close()
        # close auxiliary pulsers
        #map(lambda x: x.shutdown(), self.auxiliaryPulsers)
        for p in self.auxiliaryPulsers:
            p.shutdown()

    def saveConfig(self):
        self.config['MainWindow.State'] = self.parent.saveState()
        for tab in self.tabDict.values():
            tab.saveConfig()
        self.config['MainWindow.currentIndex'] = self.tabWidget.currentIndex()
        self.config['MainWindow.pos'] = self.pos()
        self.config['MainWindow.size'] = self.size()
        self.config['MainWindow.isMaximized'] = self.isMaximized()
        self.config['Settings.loggingLevel'] = self.loggingLevel
        self.config['Settings.consoleMaximumLinesNew'] = self.consoleMaximumLines
        self.config['Settings.ShutterNameDict'] = self.shutterNameDict 
        self.config['SettingsTriggerNameDict'] = self.triggerNameDict 
        self.config['Settings.consoleEnable'] = self.consoleEnable 
        self.pulseProgramDialog.saveConfig()
        self.scriptingWindow.saveConfig()
        self.userFunctionsEditor.saveConfig()
        self.shutterUi.saveConfig()
        self.triggerUi.saveConfig()
        self.dedicatedCountersWindow.saveConfig()
        self.logicAnalyzerWindow.saveConfig()
        if self.voltagesEnabled:
            if self.voltageControlWindow:
                self.voltageControlWindow.saveConfig()
        self.ExternalParametersSelectionUi.saveConfig()
        self.globalVariablesUi.saveConfig()
        self.loggerUi.saveConfig()
        self.todoList.saveConfig()
        self.preferencesUi.saveConfig()
        self.measurementLog.saveConfig()
        self.valueHistoryUi.saveConfig()
        self.ExternalParametersUi.saveConfig()
        list(map(lambda x: x.saveConfig(), self.objectListToSaveContext))  # call saveConfig() for each element in the list
        for awgUi in self.AWGUiDict.values():
            awgUi.saveConfig()
        
    def onProjectSelection(self):
        ui = ProjectInfoUi(self.project)
        ui.show()
        ui.exec_()
        
    def getCurrentTab(self):
        index = self.tabWidget.currentIndex()
        return self.tabDict.keyAt(index), self.tabDict.at(index)
    
    def setCurrentTab(self, name):
        self.onCurrentChanged(self.tabDict.index(name))

    def onPrint(self, target):
        """Print action is triggered on 'target', which is a plot name"""
        if hasattr( self.currentTab, 'onPrint' ):
            printer = QtPrintSupport.QPrinter(mode=QtPrintSupport.QPrinter.ScreenResolution)
            if self.preferencesUi.preferences().printPreferences.doPrint:
                dialog = QtPrintSupport.QPrintDialog(printer, self)
                dialog.setWindowTitle("Print Document")
                if dialog.exec_() != QtWidgets.QDialog.Accepted:
                    return
            printer.setResolution(self.preferencesUi.preferences().printPreferences.printResolution)
    
            pdfPrinter = QtPrintSupport.QPrinter()
            pdfPrinter.setOutputFormat(QtPrintSupport.QPrinter.PdfFormat)
            pdfPrinter.setOutputFileName(DataDirectory.DataDirectory().sequencefile(target+".pdf")[0])
            self.currentTab.onPrint(target, printer, pdfPrinter, self.preferencesUi.preferences().printPreferences)

    def onShowDocumentation(self):
        url = "file://" + os.path.join(os.path.dirname(os.path.abspath(__file__)),"docs/_build/html/index.html")
        webbrowser.open(url, new=2)

    def show(self):
        """show ExperimentUi, and any of the other main windows which were previously visible"""
        super(ExperimentUi, self).show()

        # restore dock state of ScanExperiment. Because ScanExperiment is a child QMainWindow of ExperimentUi
        # (rather than an independent window), restoreState must be called after show() is called on the parent
        # widget in order to work properly.
        for tab in self.tabDict.values():
            tabStateName = tab.experimentName+'.MainWindow.State'
            if tabStateName in self.config:
                tab.restoreState(self.config[tabStateName])

        pulseProgramVisible = self.config.get(self.pulseProgramDialog.configname+'.isVisible', True) #pulse program defaults to visible
        if pulseProgramVisible: self.pulseProgramDialog.show()
        else: self.pulseProgramDialog.hide()

        scriptingWindowVisible = self.config.get(self.scriptingWindow.configname+'.isVisible', False)
        if scriptingWindowVisible: self.scriptingWindow.show()
        else: self.scriptingWindow.hide()

        userFunctionsEditorVisible = self.config.get(self.userFunctionsEditor.configname+'.isVisible', False)
        if userFunctionsEditorVisible: self.userFunctionsEditor.show()
        else: self.userFunctionsEditor.hide()

        if self.voltagesEnabled:
            voltageControlWindowVisible = getattr(self.voltageControlWindow.settings, 'isVisible', False)
            if voltageControlWindowVisible: self.voltageControlWindow.show()
            else: self.voltageControlWindow.hide()

        if self.AWGUiDict:
            for awgUi in self.AWGUiDict.values():
                awgUiVisible = self.config.get(awgUi.configname+'.isVisible', False)
                if awgUiVisible: awgUi.show()
                else: awgUi.hide()

        self.setFocus(True)

    def setupFPGAs(self):
        """Setup all Opal Kelly FPGAs"""
        self.dac = DACController() #100 channel DAC board

        #determine name of FPGA used for Pulser, if any
        pulserName=None
        pulserSoftwareEnabled = self.project.isEnabled('software', 'Pulser')
        if pulserSoftwareEnabled:
            pulserHardware = next(iter(pulserSoftwareEnabled.values()))['hardware']
            hardwareObjName, hardwareName = project.fromFullName(pulserHardware)
            if hardwareObjName=='Opal Kelly FPGA':
                pulserName=hardwareName
        self.settings = FPGASettings() #settings for pulser specifically

        #determine name of FPGA used for DAC, if any
        dacName=None
        voltageSoftwareEnabled = self.project.isEnabled('software', 'Voltages')
        if voltageSoftwareEnabled:
            voltageHardware = next(iter(voltageSoftwareEnabled.values()))['hardware']
            hardwareObjName, hardwareName = project.fromFullName(voltageHardware)
            if hardwareObjName=='Opal Kelly FPGA':
                dacName=hardwareName

        self.OK_FPGA_Dict = self.pulser.listBoards() #list all connected Opal Kelly FPGA boards
        logger.info( "Opal Kelly Devices found: {0}".format({k:v.modelName for k,v in self.OK_FPGA_Dict.items()}) )

        enabledFPGAs = self.project.isEnabled('hardware', 'Opal Kelly FPGA') #Dict of enabled FPGAs
        for FPGAName, FPGAConfig in enabledFPGAs.items():
            FPGA = self.pulser if FPGAName==pulserName else (self.dac if FPGAName==dacName else OKBase())
            deviceName=FPGAConfig.get('device') #The 'device' field of an FPGA should be the identifier of the FPGA.
            if not deviceName:
                logger.error("No FPGA specified: 'device' field missing in Opal Kelly FPGA: '{0}' config".format(FPGAName))
            elif deviceName not in self.OK_FPGA_Dict:
                logger.error("FPGA device {0} specified in Opal Kelly FPGA: '{1}' config cannot be found".format(deviceName, FPGAName))
            else:
                device=self.OK_FPGA_Dict[deviceName]
                FPGA.openBySerial(device.serial)
                bitFile=FPGAConfig.get('bitFile')
                checkFileValid(bitFile, 'bitfile', FPGAName)
                if FPGAName==pulserName:
                    configFile = os.path.splitext(bitFile)[0] + '.xml'
                    checkFileValid(configFile, 'config file', FPGAName)
                if FPGAConfig.get('uploadOnStartup'):
                    FPGA.uploadBitfile(bitFile)
                    logger.info("Uploaded file '{0}' to {1} (model {2}) in Opal Kelly FPGA: '{3}' config".format(bitFile, deviceName, device.modelName, FPGAName))
                if FPGAName==pulserName:   # check and make sure correct hardware is loaded
                    try:
                        FPGA.pulserConfiguration(configFile)
                    except PulserHardwareException:
                        logger.exception('PulserHardwareException occurred, likely because firmware must be uploaded. Please restart the program and upload the firmware.')
                        if not self.project.exptConfig['showGui']: #force the GUI to be shown next time
                            self.project.exptConfig['showGui'] = True
                            with open(self.project.exptConfigFilename, 'w') as f:
                                yaml.dump(self.project.exptConfig, f, default_flow_style=False)
                        sys.exit("Please restart program and upload firmware (config GUI will be shown)")
                if FPGA==self.pulser:
                    self.settings.deviceSerial = device.serial
                    self.settings.deviceDescription = device.identifier
                    self.settings.deviceInfo = device
        pulserHardwareId = self.pulser.hardwareConfigurationId()
        if pulserHardwareId:
            logger.info("Pulser Configuration {0:x}".format(pulserHardwareId))
        else:
            logger.error("No pulser available")

    def instantiateAuxiliaryPulsers(self):
        self.auxiliaryPulsers = list()
        for FPGAName, FPGAConfig in self.project.isEnabled('hardware', 'Auxiliary Pulser').items():
            FPGA = PulserHardware()
            deviceName=FPGAConfig.get('device') #The 'device' field of an FPGA should be the identifier of the FPGA.
            if not deviceName:
                logger.error("No FPGA specified: 'device' field missing in Auxiliary Opal Kelly FPGA: '{0}' config".format(FPGAName))
            elif deviceName not in self.OK_FPGA_Dict:
                logger.error("FPGA device {0} specified in Auxiliary Opal Kelly FPGA: '{1}' config cannot be found".format(deviceName, FPGAName))
            else:
                device=self.OK_FPGA_Dict[deviceName]
                FPGA.openBySerial(device.serial)
                bitFile=FPGAConfig.get('bitFile')
                checkFileValid(bitFile, 'bitfile', FPGAName)
                configFile = os.path.splitext(bitFile)[0] + '.xml'
                checkFileValid(configFile, 'config file', FPGAName)
                if FPGAConfig.get('uploadOnStartup'):
                    FPGA.uploadBitfile(bitFile)
                    logger.info("Uploaded file '{0}' to {1} (model {2}) in Opal Kelly FPGA: '{3}' config".format(bitFile, deviceName, device.modelName, FPGAName))
                FPGA.pulserConfiguration(configFile)
                pulserHardwareId = self.pulser.hardwareConfigurationId()
                if pulserHardwareId:
                    logger.info("Auxiliary Pulser {1} Configuration {0:x}".format(pulserHardwareId, FPGAName))
                else:
                    logger.error("No pulser available")
                if FPGAConfig.get('PulserParameters'):
                    ui, _ = self.instantiateParametersUi(FPGA, "{0} Pulser Config".format(FPGAName),
                                                 "{0}.PulserParameterUi".format(FPGAName), self.config, self.globalVariablesUi.globalDict)
                    self.objectListToSaveContext.append(ui)
                if FPGAConfig.get('DDS'):
                    ui, _ = self.instantiateDDSUi(FPGA, "{0} DDS".format(FPGAName), "{0}.DDSUi".format(FPGAName), self.config, self.globalVariablesUi.globalDict)
                    self.objectListToSaveContext.append(ui)
                if FPGAConfig.get('DAC'):
                    ui, _ = self.instantiateDACUi(FPGA, "{0} DAC".format(FPGAName), "{0}.dacUi".format(FPGAName), self.config, self.globalVariablesUi.globalDict)
                    self.objectListToSaveContext.append(ui)
                if FPGAConfig.get('Shutters'):
                    ui, _ = self.instantiateShutterUi(FPGA, "{0} Shutter".format(FPGAName), "{0}.ShutterUi".format(FPGAName), self.config, self.globalVariablesUi.globalDict, None, None)
                    self.objectListToSaveContext.append(ui)
                self.auxiliaryPulsers.append(FPGA)
class VoltageGlobalAdjust(VoltageGlobalAdjustForm, VoltageGlobalAdjustBase ):
    updateOutput = QtCore.pyqtSignal(object, object)
    outputChannelsChanged = QtCore.pyqtSignal(object)
    _channelParams = {}
    
    def __init__(self, config, globalDict, parent=None):
        VoltageGlobalAdjustForm.__init__(self)
        VoltageGlobalAdjustBase.__init__(self, parent)
        self.config = config
        self.configname = 'VoltageGlobalAdjust.Settings'
        self.settings = self.config.get(self.configname, Settings())
        self.globalAdjustDict = SequenceDict()
        self.myLabelList = list()
        self.myBoxList = list()
        self.historyCategory = 'VoltageGlobalAdjust'
        self.adjustHistoryName = None
        self.globalDict = globalDict
        self.adjustCache = self.config.get(self.configname+".cache", dict()) 
        self.savedValue = defaultdict( lambda: None )
        self.displayValueObservable = defaultdict( lambda: Observable() )

    def setupUi(self, parent):
        VoltageGlobalAdjustForm.setupUi(self, parent)
        self.gainBox.setValue(self.settings.gain)
        self.gainBox.valueChanged.connect( self.onGainChanged )
        self.tableModel = VoltageGlobalAdjustTableModel( self.globalAdjustDict, self.globalDict )
        self.tableView.setModel( self.tableModel )
        self.tableView.setSortingEnabled(True)   # triggers sorting
        self.delegate =  MagnitudeSpinBoxDelegate(self.globalDict)
        self.tableView.setItemDelegateForColumn(1, self.delegate)
        
    def onGainChanged(self, gain):
        self.settings.gain = gain
        self.updateOutput.emit(self.globalAdjustDict, self.settings.gain)        
        
    def setupGlobalAdjust(self, name, adjustDict):
        if name!=self.adjustHistoryName:
            self.adjustCache[self.adjustHistoryName] = [v.data for v in list(self.globalAdjustDict.values())]
            self.settings.gainCache[self.adjustHistoryName] = self.settings.gain
            self.settings.gain = self.settings.gainCache.get( name, self.settings.gain )
            if name in self.adjustCache:
                for data in self.adjustCache[name]:
                    if data[0] in adjustDict:
                        adjustDict[data[0]].data = data
            self.adjustHistoryName = name
        self.globalAdjustDict = adjustDict
        for name, adjust in self.globalAdjustDict.items():
            try:
                adjust.valueChanged.connect(self.onValueChanged, QtCore.Qt.UniqueConnection)
            except:
                pass
        self.tableModel.setGlobalAdjust( adjustDict )
        self.outputChannelsChanged.emit( self.outputChannels() )
        self.gainBox.setValue(self.settings.gain)
        #self.updateOutput.emit(self.globalAdjustDict, self.settings.gain)        
        
    def onValueChanged(self, name, value, string, origin):
        if origin=='recalculate':
            self.tableModel.valueRecalcualted(name)
        self.globalAdjustDict[name]._value = float(self.globalAdjustDict[name]._value)
        self.updateOutput.emit(self.globalAdjustDict, self.settings.gain)
    
    def saveConfig(self):
        self.config[self.configname] = self.settings
        self.adjustCache[self.adjustHistoryName] = [v.data for v in list(self.globalAdjustDict.values())]
        self.config[self.configname+".cache"] = self.adjustCache
        
    def setValue(self, channel, value):
        self.globalAdjustDict[channel].value = value 
        return value

    def getValue(self, channel):
        return self.globalAdjustDict[channel].value
    
    def currentValue(self, channel):
        return self.globalAdjustDict[channel].value
    
    def saveValue(self, channel):
        self.savedValue[channel] = self.globalAdjustDict[channel].value
    
    def restoreValue(self, channel):
        if self.savedValue[channel] is not None:
            self.globalAdjustDict[channel].value = self.savedValue[channel]
        return True
    
    def strValue(self, channel):
        adjust = self.globalAdjustDict[channel]
        return adjust.string if adjust.hasDependency else None
    
    def setStrValue(self, channel, value):
        pass
    
    def outputChannels(self):
        self._outputChannels = dict(( (channelName, VoltageOutputChannel(self, None, channelName, self.globalDict, )) for channelName in self.globalAdjustDict.keys() ))
        return self._outputChannels