Ejemplo n.º 1
0
class FieldCoil(Battery):
    def __init__(self):
        #aoChannel = daq.AoChannel('USB6002_B/ao0', -10, +10)
        #aiChannel = daq.AiChannel('USB6002_B/ai1', -10, +10)
        aoChannel = daq.AoChannel('Dev2/ao0', -10, +10)
        aiChannel = daq.AiChannel('Dev2/ai0', -10, +10)
        aiChannel.setTerminalConfiguration(daq.AiChannel.TerminalConfiguration.DIFF)
        #aiChannel = None
        self.publisher = ZmqPublisher(origin='FieldCoilBiasDAQ', port=PubSub.FieldCoilBiasDAQ)
        Battery.__init__(self, aoChannel, aiChannel)

    def _writeData(self, aoTask, V):
        '''Override the superclass atomic write to also do ZMQ publish.'''
        aoTask.writeData([V], autoStart = True)
        self.publisher.publishDict('Coil', {'t':time.time(), 'Vcoil':V})
Ejemplo n.º 2
0
class LockinThermometerWidget(Ui.Ui_Form, QWidget):
    def __init__(self, parent=None):
        super(LockinThermometerWidget, self).__init__(parent)
        self.setupUi(self)
        self.setWindowTitle('Lockin Thermometer')
        self.serverThread = None
        self.timer = None
        self.Rthermometer = float('nan')

        axis = pg.DateAxisItem(orientation='bottom')
        self.plot = pg.PlotWidget(axisItems={'bottom': axis})
        self.plot.setBackground('w')
        self.plot.plotItem.showGrid(x=True, y=True)
        self.plot.addLegend()
        self.verticalLayout.addWidget(self.plot)
        self.curve = pg.PlotCurveItem(name='X', symbol='o', pen='b')
        self.plot.addItem(self.curve)
        self.clearPb.clicked.connect(self.clearData)
        self.clearData()
        self.plotYAxisCombo.currentIndexChanged.connect(self.updatePlot)

        self.sr830 = None
        self.runPb.clicked.connect(self.run)
        self.parameterItems = [
            self.attenuatorGainSb, self.sourceImpedanceSb,
            self.driveResistanceSb, self.leadResistanceSb, self.preampGainSb,
            self.sensorVoltageSb
        ]
        self.savePb.clicked.connect(self.saveParameterSet)
        self.loadPb.clicked.connect(self.loadParameterSet)
        self.deletePb.clicked.connect(self.deleteParameterSet)
        self.attenuatorAttenuationSb.valueChanged.connect(
            self.updateAttenuatorGain)
        self.attenuatorGainSb.valueChanged.connect(
            self.updateAttenuatorAttenuation)
        self.sensorVoltageIndicator.setUnit('V')
        self.sensorCurrentIndicator.setUnit('A')
        self.sensorPowerIndicator.setUnit('W')

        sr830 = SR830(None)
        sr830.sensitivity.populateEnumComboBox(self.minSensitivityCombo)

        self.loadParameterSets()
        self.restoreSettings()
        self.hkSub = HousekeepingSubscriber(parent=self)
        self.hkSub.adrResistanceReceived.connect(self.collectAdrResistance)
        self.hkSub.start()
        self.adrResistanceIndicator.setUnit(u'Ω')
        self.adrResistanceIndicator.setPrecision(5)
        self.publisher = None

        combo = self.calibrationCombo
        for i, calId in enumerate(ThermometerCalIds):
            try:
                cal = getThermometerCalibration(calId)
                info = cal.info
            except Exception as e:
                import warnings
                warnings.warn(
                    'Calibration %s unavailable due to exception %s' %
                    (calId, str(e)))
            combo.addItem(calId)
            combo.setItemData(i, info, Qt.ToolTipRole)

        self.selectCalibration()
        combo.currentIndexChanged.connect(self.selectCalibration)

    def startServerThread(self, port):
        if self.serverThread is not None:
            self.serverThread.stop()
            self.serverThread.wait(1000)
            del self.serverThread
            self.serverThread = None
        self.serverThread = RequestReplyThreadWithBindings(port, parent=self)
        boundWidgets = {
            'adjustExcitation': self.adjustExcitationCb,
            'sensorVoltage': self.sensorVoltageSb,
            'tolerance': self.toleranceSb,
            'autoRanging': self.autoRangingCb
        }
        for name in boundWidgets:
            self.serverThread.bindToWidget(name, boundWidgets[name])
        #logger.info('Starting server thread')
        self.serverThread.start()

    def selectCalibration(self):
        calId = str(self.calibrationCombo.currentText())
        self.calibration = getThermometerCalibration(calId)

    def collectAdrResistance(self, R):
        self.Rthermometer = R
        self.adrResistanceIndicator.setValue(R)

    def updateAttenuatorGain(self, v):
        sb = self.attenuatorGainSb
        block = sb.blockSignals(True)
        sb.setValue(1. / v)
        sb.blockSignals(block)

    def updateAttenuatorAttenuation(self, v):
        sb = self.attenuatorAttenuationSb
        block = sb.blockSignals(True)
        sb.setValue(1. / v)
        sb.blockSignals(block)

    def saveParameterSet(self):
        s = QSettings()
        s.beginGroup('ParameterSets')
        name = self.configCombo.currentText()
        s.beginGroup(name)
        s.setValue('adjustExcitation', self.adjustExcitationCb.isChecked())
        s.setValue('sensorName', self.sensorNameLe.text())
        s.setValue('sr830Visa', self.visaCombo.currentText())
        s.setValue('autoRanging', self.autoRangingCb.isChecked())
        s.setValue('minSensitivity', self.minSensitivityCombo.currentCode())
        for item in self.parameterItems:
            s.setValue(item.objectName(), item.value())
        s.endGroup()
        s.endGroup()

    def loadParameterSet(self):
        s = QSettings()
        name = self.configCombo.currentText()
        s.beginGroup('ParameterSets')
        if not name in s.childGroups():
            dlg = QErrorMessage(self)
            dlg.setWindowTitle('Error')
            dlg.showMessage('No saved parameters available for %s' % name)
            return
        s.beginGroup(name)
        for item in self.parameterItems:
            item.setValue(s.value(item.objectName(), item.value(), type=float))
        self.adjustExcitationCb.setChecked(
            s.value('adjustExcitation', False, type=bool))
        self.sensorNameLe.setText(s.value('sensorName', '', type=QString))
        self.visaCombo.setCurrentIndex(
            self.visaCombo.findText(
                s.value('sr830Visa', 'GPIB0::12', type=QString)))
        self.autoRangingCb.setChecked(s.value('autoRanging', True, type=bool))
        self.minSensitivityCombo.setCurrentCodeSilently(
            s.value('minSensitivity', 0, type=int))
        s.endGroup()
        s.endGroup()

    def loadParameterSets(self):
        s = QSettings()
        s.beginGroup('ParameterSets')
        names = s.childGroups()
        self.configCombo.addItems(names)

    def deleteParameterSet(self):
        i = self.configCombo.currentIndex()
        name = self.configCombo.itemText(i)

        s = QSettings()
        s.beginGroup('ParameterSets')
        s.beginGroup(name)
        s.remove('')
        s.endGroup()
        s.endGroup()

        self.configCombo.removeItem(i)

    def closeEvent(self, event):
        if self.timer:
            self.timer.stop()

        self.saveSettings()
        self.hkSub.stop()
        self.hkSub.wait(1000)

    def restoreSettings(self):
        s = QSettings()
        #visa = s.value('visa', QString(), type=QString)
        #i = self.visaCombo.findText(visa)
        #elf.visaCombo.setCurrentIndex(i)
        self.configCombo.setCurrentIndex(
            self.configCombo.findText(s.value('parameterSet', '',
                                              type=QString)))
        if len(self.configCombo.currentText()):
            self.loadParameterSet()
        #self.sensorNameLe.setText(s.value('sensorName', '', type=QString))

    def saveSettings(self):
        s = QSettings()
        #s.setValue('visa', self.visaCombo.currentText())
        s.setValue('parameterSet', self.configCombo.currentText())
        #s.setValue('sensorName', self.sensorNameLe.text())

    def enableWidgets(self, enable):
        self.visaCombo.setEnabled(enable)
        self.attenuatorGroupBox.setEnabled(enable)
        self.seriesResistanceGroupBox.setEnabled(enable)
        self.preampGroupBox.setEnabled(enable)
        self.sensorNameLe.setEnabled(enable)
        self.loadPb.setEnabled(enable)
        self.savePb.setEnabled(enable)
        self.deletePb.setEnabled(enable)
        self.configCombo.setEnabled(enable)

    def run(self):
        if self.sr830 is not None:
            self.stop()
        else:
            self.start()

    def stop(self):
        self.timer.stop()
        self.timer = None
        self.sr830 = None
        self.runPb.setText('Start')
        self.enableWidgets(True)
        del self.publisher
        self.publisher = None

    def sensorName(self):
        return str(self.sensorNameLe.text())

    def start(self):
        sensorName = self.sensorName()
        self.setWindowTitle('Lock-In Thermometer %s' % sensorName)
        setAppId(sensorName)

        if sensorName == 'BusThermometer':
            icon = QIcon('Icons/LockinThermometer_Bus.ico')
        elif sensorName == 'RuOx2005Thermometer':
            icon = QIcon('Icons/LockinThermometer_BoxOutside.ico')
        elif sensorName == 'BoxThermometer':
            icon = QIcon('Icons/LockinThermometer_BoxInside2.ico')
        else:
            icon = QIcon('Icons/LockinThermometer.ico')

        self.setWindowIcon(icon)

        visa = str(self.visaCombo.currentText())
        self.sr830 = SR830(visa)
        #self.sr830.debug = True

        self.sr830.readAll()
        self.sr830.sineOut.caching = False  # Disable caching on this

        self.publisher = ZmqPublisher('LockinThermometer',
                                      LockInPubSub(sensorName))
        self.startServerThread(LockInRequestReply(sensorName))

        self.runPb.setText('Stop')
        self.timer = QTimer()
        self.timer.setInterval(1000)
        self.timer.timeout.connect(self.snapSignal)
        self.timer.start()
        self.enableWidgets(False)
        self.rangeChangedTime = 0
        self.exChangedTime = 0
        t = time.time()
        timeString = time.strftime('%Y%m%d-%H%M%S', time.localtime(t))
        dateString = time.strftime('%Y%m%d')
        sensorName = str(self.sensorNameLe.text())

        s = QSettings('WiscXrayAstro', application='ADR3RunInfo')
        path = str(s.value('runPath', '', type=str))
        fileName = os.path.join(path, '%s_%s.dat' % (sensorName, dateString))
        if not os.path.isfile(fileName):  # Maybe create new file
            with open(fileName, 'a+') as f:
                f.write('#LockinThermometer.py\n')
                f.write('#Date=%s\n' % timeString)
                f.write('#SensorName=%s\n' % sensorName)
                f.write('#SR830=%s\n' % self.sr830.visaId())
                f.write('#AttenuatorGain=%f\n' % self.attenuatorGainSb.value())
                f.write('#AttenuatorSourceImpedance=%f\n' %
                        self.sourceImpedanceSb.value())
                f.write('#DriveResistance=%f\n' %
                        self.driveResistanceSb.value())
                f.write('#LeadResistance=%f\n' % self.leadResistanceSb.value())
                f.write('#PreampGain=%f\n' % self.preampGainSb.value())
                f.write('#DesiredExcitation=%f\n' %
                        self.sensorVoltageSb.value())
                k = self.sr830.allSettingValues()
                for key, value in k.iteritems():
                    f.write('#SR830/%s=%s\n' % (key, value))
                f.write('#' + '\t'.join([
                    'time', 'VsineOut', 'X', 'Y', 'f', 'Sensitivity', 'RxCalc',
                    'Rtherm'
                ]) + '\n')
        self.fileName = fileName

    def snapSignal(self):
        t = time.time()
        try:
            self.sr830.snapSignal()
        except CommunicationsError as e:
            # TODO Log the error
            self.sr830.clearGarbage()
            return

        VsineOut = self.sr830.sineOut.value
        X = self.sr830.X
        Y = self.sr830.Y
        f = self.sr830.f

        rangeChangeAge = t - self.rangeChangedTime
        exChangeAge = t - self.exChangedTime

        sensitivity = self.sr830.sensitivity.value

        if self.autoRangingCb.isChecked():
            self.sr830.checkStatus()
            minCode = self.minSensitivityCombo.currentCode()
            currentCode = self.sr830.sensitivity.code
            if self.sr830.overload and rangeChangeAge > 10:
                self.sr830.sensitivity.code = currentCode + 1
                self.rangeChangeTime = t
            elif abs(X) > 0.9 * sensitivity and rangeChangeAge > 10:
                self.sr830.sensitivity.code = currentCode + 1
                self.rangeChangedTime = t
            elif abs(X) < 0.3 * sensitivity and rangeChangeAge > 10:
                if currentCode > minCode:
                    self.sr830.sensitivity.code = currentCode - 1
                    self.rangeChangedTime = t
            elif currentCode < minCode:
                self.sr830.sensitivity.code = minCode
                self.rangeChangedTime = t

        G1 = self.attenuatorGainSb.value()
        G2 = self.preampGainSb.value()
        Rsource = self.sourceImpedanceSb.value()
        Rd = self.driveResistanceSb.value()
        Rl = self.leadResistanceSb.value()
        Rs = Rsource + Rd + Rl

        Vx = X / G2

        Vex = VsineOut * G1  # Real excitation
        self.sensorVoltageIndicator.setValue(Vx)

        Rx = Rs / (Vex / abs(Vx) - 1.)
        I = abs(Vx) / Rx
        self.sensorCurrentIndicator.setValue(I)
        P = abs(Vx) * I
        Temp = self.calibration.calculateTemperature(
            [Rx])[0]  # @todo This is really a crutch

        Tbase = self.calibration.correctForReadoutPower(Temp, P)

        if self.publisher is not None:
            if rangeChangeAge > 10 and exChangeAge > 10 and Temp == Temp and Temp > 0 and Temp < 10:
                self.publisher.publishDict(self.sensorName(), {
                    't': t,
                    'R': Rx,
                    'T': Temp,
                    'P': P,
                    'Tbase': Tbase
                })
                #self.publisher.publish('ADR_Sensor_R', Rx)
                #self.publisher.publish('ADR_Temperature', Temp)

        # Log data
        with open(self.fileName, 'a+') as of:
            of.write(
                '%.3f\t%.3f\t%.5E\t%.5E\t%.3f\t%.1E\t%.5E\t%.5E\n' %
                (t, VsineOut, X, Y, f, sensitivity, Rx, self.Rthermometer))

        self.ts.append(t)
        self.xs.append(X)
        self.ys.append(Y)
        self.fs.append(f)
        self.VsineOuts.append(VsineOut)
        self.Rs.append(Rx)
        self.Vxs.append(Vx)
        self.Ps.append(P)
        self.Ts.append(Temp)
        self.Tbases.append(Tbase)

        self.sensorIndicator.setValue(Rx)
        self.temperatureIndicator.setKelvin(Temp)
        self.baseTempIndicator.setKelvin(Tbase)
        self.sensorPowerIndicator.setValue(P)
        self.updateLed.flashOnce()
        self.updatePlot()

        # Not sure where this code came from. Seems questionable (FJ)
        # if len(self.Ts) > 2 :
        #     dTdt = abs((self.Ts[-1] - self.Ts[-2]) / (self.ts[-1] - self.ts[-2]))
        #     if dTdt > 0.1:
        #         self.temperatureIndicator.setKelvin('N/A')
        #         self.stop()

        # Perhaps change excitation
        if exChangeAge < 10 or rangeChangeAge < 10 or not self.adjustExcitationCb.isChecked(
        ):
            return
        VxDesired = self.sensorVoltageSb.value()
        IDesired = VxDesired / Rx
        VexDesired = IDesired * (Rx + Rs)
        change = (VexDesired - Vex) / Vex
        tolerance = 1E-2 * self.toleranceSb.value()
        if abs(change) < tolerance:
            return

        VsineOutDesired = VexDesired / G1  # This is what we would like to see
        # What we actually get may be something different
        Vnew = min(5, max(VsineOutDesired, 0.004))
        if tolerance == 0 and abs(
                Vnew -
                VsineOut) > 0.009:  # If a large step is required, do it slowly
            Vnew = (3. * VsineOut + 1. * Vnew) / 4.

        if abs(Vnew - VsineOut) < 0.002:
            return
        self.exChangedTime = t
        self.sr830.sineOut.value = Vnew

    def clearData(self):
        self.ts = []
        self.xs = []
        self.ys = []
        self.fs = []
        self.Rs = []
        self.Ps = []
        self.VsineOuts = []
        self.Vxs = []
        self.Ts = []
        self.Tbases = []
        self.updatePlot()

    def updatePlot(self):
        yAxis = self.plotYAxisCombo.currentText()
        pl = self.plot
        if yAxis == 'X':
            y = self.xs
            pl.setLabel('left', 'Lock-in X', 'V')
        elif yAxis == 'Y':
            y = self.ys
            pl.setLabel('left', 'Lock-in Y', 'V')
        elif yAxis == 'R':
            y = self.Rs
            pl.setLabel('left', 'R sensor', u'Ω')
        elif yAxis == 'V sine out':
            y = self.VsineOuts
            pl.setLabel('left', 'V sine out', 'V')
        elif yAxis == 'V sensor':
            y = self.Vxs
            pl.setLabel('left', 'V sensor', 'V')
        elif yAxis == 'P sensor':
            y = self.Ps
            pl.setLabel('left', 'P sensor', 'W')
        elif yAxis == 'Sensor temperature':
            y = self.Ts
            pl.setLabel('left', 'T sensor', 'K')
        elif yAxis == 'Base temperature':
            y = self.Tbases
            pl.setLabel('left', 'T base', 'K')

        x = self.ts
        self.curve.setData(x, y)
Ejemplo n.º 3
0
class Tes(object):
    class States:
        Idle = 0
        RampAboveBias = 1
        RampBias = 2
        WaitForTlow = 3
        HoldLow = 4
        WaitForThigh = 5
        HoldHigh = 6
        WaitForTmid = 7
        HoldTmid = 8
        
    def __init__(self, Rbias, biasChannel, fieldChannel=None, pflChannel = None):
        self.publisher = ZmqPublisher(origin='TesBiasDAQ', port=PubSub.TesBiasDAQ)
        self.Rbias = Rbias
        self.biasChannel = biasChannel
        self.pflChannel = pflChannel
        self.Vbias = 0
        
    def _makeAoTask(self):
        aoTask = daq.AoTask('biasTask')
        aoTask.addChannel(self.biasChannel)
        return aoTask

    def _makeAiTask(self):
        aiTask = daq.AiTask('pflTask')
        aiTask.addChannel(self.pflChannel)
        return aiTask
      
    def setBias(self, Ibias):
        aoTask = self._makeAoTask()
        Vbias = Ibias * self.Rbias
        #logger.info('Applying Vbias=%.5f V' % Vbias)
        self._writeData(aoTask, Vbias)
        self.Vbias = Vbias
        aoTask.stop()
        aoTask.clear()

    def _writeData(self, aoTask, V):
        '''Actually update the voltage and publish to ZMQ.'''
        aoTask.writeData([V], autoStart = True) # This is the only place where aoTask.writeData should appear
        self.publisher.publishDict('Coil', {'t':time.time(), 'Vbias':V})
        
    def rampBias(self, Ibias, Vstep=0.001):
        aoTask = self._makeAoTask()
        Vbias = Ibias * self.Rbias
        step = Vstep * np.sign(Vbias-self.Vbias)
        Vs = np.arange(self.Vbias, Vbias+step, step)
        for V in Vs:
            self._writeData(aoTask, V)
        self._writeData(aoTask, Vbias)
        self.Vbias = Vbias
        aoTask.stop()
        aoTask.clear()
        
    def logRampBias(self, Ibias, nSteps, V0=0):
        assert Ibias > 0
        assert self.Vbias > 0
        aoTask = self._makeAoTask()
        Vbias = Ibias * self.Rbias
        Vs = V0+np.logspace(np.log10(self.Vbias-V0), np.log10(Vbias-V0), nSteps)
        for V in Vs:
            self._writeData(aoTask, V)
        self._writeData(aoTask, Vbias)
        self.Vbias = Vbias
        aoTask.stop()
        aoTask.clear()
        
    def measureIvsT(self, adr, Ibias, Tmid, deltaT, fileName=''):
        assert deltaT > 0
        VbiasTarget = Ibias * self.Rbias
        VbiasTargetAbove = 3.5
        aoTask = self._makeAoTask()
        aiTask = self._makeAiTask()
        state = Tes.States.Idle
        ts = []
        Vsquids = []
        Vbiases = []
        Tadrs = []
        step = 0.02
        nHold = 40
        
        while True:
            time.sleep(0.1)
            QApplication.processEvents()
            Vsquid = np.mean(aiTask.readData(500)[0])
            t = time.time()
            ts.append(time.time())
            Vsquids.append(Vsquid)
            Vbiases.append(self.Vbias)
            T=adr.T
            Tadrs.append(T)
            #print('State=', state, self.Vbias, Vsquid)
            if len(fileName):
                with open(fileName, 'a') as f:
                    f.write('%.3f\t%d\t%.5f\t%.6f\t%.6f\n' % (t, state, self.Vbias, T, Vsquid))
            n = len(Vsquids)
            if state == Tes.States.Idle:
                if n > nHold:
                    state = Tes.States.RampAboveBias
            elif state == Tes.States.RampAboveBias:
                s = step * np.sign(VbiasTargetAbove - self.Vbias)
                Vbias = self.Vbias + s
                if s < 0:
                    Vbias = max(VbiasTargetAbove, Vbias)
                else:
                    Vbias = min(VbiasTargetAbove, Vbias)
                self._writeData(aoTask, Vbias)
                self.Vbias = Vbias
                if abs(self.Vbias-VbiasTargetAbove) < 1E-5:
                    state = Tes.States.RampBias
                    nTransition = n
            elif state == Tes.States.RampBias:
                s = step * np.sign(VbiasTarget - self.Vbias)
                Vbias = self.Vbias + s
                if s < 0:
                    Vbias = max(VbiasTarget, Vbias)
                else:
                    Vbias = min(VbiasTarget, Vbias)
                self._writeData(aoTask, Vbias)
                self.Vbias = Vbias
                if abs(self.Vbias-VbiasTarget) < 1E-5:
                    adr.rampTo(Tmid-deltaT)
                    state = Tes.States.WaitForTlow
                    nTransition = n
            elif state == Tes.States.WaitForTlow:
                if adr.T <= Tmid - deltaT:
                    state = Tes.States.HoldLow
                    nTransition = n
            elif state == Tes.States.HoldLow:
                if n > nTransition + nHold:
                    state = Tes.States.WaitForThigh
                    adr.rampTo(Tmid + deltaT)
                    nTransition = n
            elif state == Tes.States.WaitForThigh:
                if adr.T >= Tmid + deltaT:
                    state = Tes.States.HoldHigh
                    nTransition = n
            elif state == Tes.States.HoldHigh:
                if n > nTransition + nHold:
                    state = Tes.States.WaitForTmid
                    adr.rampTo(Tmid)
                    nTransition = n
            elif state == Tes.States.WaitForTmid:
                if abs(adr.T - Tmid) < 20E-6:
                    state = Tes.States.HoldTmid
                    nTransition = n
            elif state == Tes.States.HoldTmid:
                if n > nTransition+nHold:
                    break
                
        return np.asarray(ts), np.asarray(Tadrs), np.asarray(Vbiases), np.asarray(Vsquids)
Ejemplo n.º 4
0
class Avs47SingleChannelWidget(Ui.Ui_Form, QWidget):
    def __init__(self, parent=None):
        super(Avs47SingleChannelWidget, self).__init__(parent)
        self.dmm = None
        self.avs = None
        self.buffer = ''
        self.workerThread = None
        self.setupUi(self)
        self.setWindowTitle('AVS47 Single Channel')
        self.outputFile = None
        self.yaxisCombo.currentIndexChanged.connect(self.switchPlotAxis)
        self.temperatureIndicator.setPrecision(5)

        combo = self.calibrationCombo
        combo.addItem('RuOx 600')
        combo.addItem('RuOx 2005')
        combo.addItem('RuOx Bus (Shuo)')
        combo.setItemData(0, 'Nominal sensor calibration for RuOx 600 series',
                          Qt.ToolTipRole)
        combo.setItemData(1, 'Calibration for RuOx sensor #2005 series',
                          Qt.ToolTipRole)
        combo.setItemData(
            2,
            'Cross-calibration against RuOx #2005 by Shuo (not so good above ~300mK)',
            Qt.ToolTipRole)
        combo.currentIndexChanged.connect(self.selectCalibration)

        axis = pg.DateAxisItem(orientation='bottom')
        self.plot = pg.PlotWidget(axisItems={'bottom': axis})
        self.plot.addLegend()
        self.verticalLayout.addWidget(self.plot)
        self.curve = pg.PlotCurveItem(name='Actual', symbol='o', pen='g')
        self.plot.addItem(self.curve)
        self.clearData()

        self.runPb.clicked.connect(self.runPbClicked)

        self.widgetsForSettings = [
            self.readoutInternalRadio, self.readoutDmmRadio,
            self.readoutZmqRadio, self.bridgeCombo, self.dmmCombo,
            self.autoRangeCb, self.rangeUpSb, self.rangeDownSb,
            self.yaxisCombo, self.intervalSb, self.calibrationCombo
        ]
        self.restoreSettings()
        self.selectCalibration()
        #self.makeFilters()

        self.publisher = ZmqPublisher('Avs47SingleChannel',
                                      PubSub.AdrTemperature)

        #self.serverThread = RequestReplyThreadWithBindings(port=RequestReply.AdrPidControl, parent=self)
        #boundWidgets = {'rampRate':self.rampRateSb, 'rampTarget':self.rampTargetSb, 'rampEnable':self.rampEnableCb, 'setpoint':self.setpointSb}
        #for name in boundWidgets:
        #self.serverThread.bindToWidget(name, boundWidgets[name])
        #self.serverThread.start()

    def makeFilters(self):
        fs = 10
        fc = 0.5
        order = 3
        self.lpfR = IIRFilter.lowpass(order=order, fc=fc, fs=fs)
        self.lpfT = IIRFilter.lowpass(order=order, fc=fc, fs=fs)
        self.lpfR.initializeFilterFlatHistory(self.Rs[-1])
        self.lpfT.initializeFilterFlatHistory(self.Ts[-1])
        Rfilt = self.lpfR.filterCausal(R)
        Tfilt = self.lpfT.filterCausal(T)

    def selectCalibration(self):
        cal = self.calibrationCombo.currentText()
        if cal == 'RuOx 600':
            self.calibration = RuOx600()
        elif cal == 'RuOx 2005':
            self.calibration = RuOx2005()
        elif cal == 'RuOx Bus (Shuo)':
            self.calibration = RuOxBus()
        else:
            warnings.warn('Unknown calibration selected:', cal)

    def switchPlotAxis(self):
        yaxis = self.yaxisCombo.currentText()
        if yaxis == 'R':
            self.plot.getAxis('left').setLabel('R', 'Ohm')
        elif yaxis == 'T':
            self.plot.getAxis('left').setLabel('T', 'K')
        self.updatePlot()

    def runPbClicked(self):
        if self.workerThread is not None:
            self.workerThread.stop()
            return
        self.avs = Avs47(self.bridgeCombo.visaResource())
        self.avs.range.bindToEnumComboBox(self.rangeCombo)
        self.avs.muxChannel.bindToSpinBox(self.channelSb)
        #self.channelSb.setValue(self.avs.muxChannel.value)
        self.avs.excitation.bindToEnumComboBox(self.excitationCombo)
        self.avs.range.caching = True
        self.avs.excitation.caching = True
        self.avs.muxChannel.caching = True

        thread = WorkerThread()
        thread.setBridge(self.avs)
        if self.readoutDmmRadio.isChecked():
            self.dmm = Agilent34401A(self.dmmCombo.visaResource())
            thread.setDmm(self.dmm)

        connectAndUpdate(self.rangeUpSb, thread.setAutoRangeUp)
        connectAndUpdate(self.rangeDownSb, thread.setAutoRangeDown)
        connectAndUpdate(self.autoRangeCb, thread.enableAutoRange)

        thread.finished.connect(self.threadFinished)
        thread.readingAvailable.connect(self.collectReading)
        self.intervalSb.valueChanged.connect(thread.setInterval)
        self.workerThread = thread
        self.t0 = time.time()
        thread.start()
        self.enableControls(False)
        self.runPb.setText('Stop')

    def threadFinished(self):
        self.workerThread.deleteLater()
        self.workerThread = None
        self.flushBuffer()
        self.runPb.setText('Start')
        self.enableControls(True)

    def appendErrorMessage(self, message):
        self.errorTextEdit.append(message)

    def enableControls(self, enable):
        self.readoutGroupBox.setEnabled(enable)
        self.bridgeCombo.setEnabled(enable)

    def restoreSettings(self):
        settings = QSettings()
        for widget in self.widgetsForSettings:
            restoreWidgetFromSettings(settings, widget)

    def saveSettings(self):
        settings = QSettings()
        for widget in self.widgetsForSettings:
            saveWidgetToSettings(settings, widget)

    def clearData(self):
        self.ts = []
        self.Rs = []
        self.Ts = []
        self.updatePlot()

    def collectReading(self, R, t):

        channel = self.avs.muxChannel.value
        excitation = self.avs.excitation.code
        Range = self.avs.range.code

        V = self.avs.excitation.value

        try:
            P = V**2 / R
        except ZeroDivisionError:
            P = np.nan

        try:
            T = self.calibration.calculateTemperature(
                [R])[0]  # @todo This is really a crutch
        except Exception as e:
            self.appendErrorMessage(e)
            T = 0

        self.ts.append(t)
        self.Rs.append(R)
        self.Ts.append(T)

        string = "%.3f\t%d\t%d\t%d\t%.6g\n" % (t, channel, excitation, Range,
                                               R)
        self.buffer += string
        if len(self.buffer) > 2048:
            self.flushBuffer()

        Sensors = {0: 'FAA', 1: 'GGG'}
        if self.publisher is not None:
            if Sensors.has_key(channel):
                sensorName = Sensors[channel]
                self.publisher.publishDict(sensorName, {
                    't': t,
                    'R': R,
                    'T': T,
                    'P': P
                })

        maxLength = 20000
        self.ts = pruneData(self.ts, maxLength)
        self.Rs = pruneData(self.Rs, maxLength)
        self.Ts = pruneData(self.Ts, maxLength)

        self.resistanceIndicator.setValue(R)
        self.temperatureIndicator.setKelvin(T)
        self.updatePlot()

    def flushBuffer(self):
        t = time.time()
        timeString = time.strftime('%Y%m%d', time.localtime(t))
        s = QSettings('WiscXrayAstro', application='ADR3RunInfo')
        path = str(s.value('runPath', '', type=str))
        fileName = os.path.join(path, 'AVSBridge_%s.dat' % timeString)

        if not os.path.isfile(fileName):  # Maybe create new file
            x = '#Avs47SingleChannel.py\n'
            x += '#Date=%s\n' % timeString
            x += '#AVS-47=%s\n' % self.avs.visaId()
            if self.dmm is not None:
                x += '#Readout=DMM\n'
                x += '#DMM=%s\n' % self.dmm.visaId()
            else:
                x += '#Readout=AVS-47\n'
            x += '#' + '\t'.join(
                ['time', 'channel', 'excitation', 'range', 'R']) + '\n'
            self.buffer = x + self.buffer

        try:
            with open(fileName, 'a') as f:
                f.write(self.buffer)
            self.buffer = ''
        except Exception as e:
            warnings.warn('Unable to write buffer to log file: %s' % e)

    def updatePlot(self):
        yaxis = self.yaxisCombo.currentText()
        if yaxis == 'R':
            self.curve.setData(self.ts, self.Rs)
        elif yaxis == 'T':
            self.curve.setData(self.ts, self.Ts)

    def closeEvent(self, e):
        if self.workerThread is not None:
            self.workerThread.stop()
            self.workerThread.wait(2000)
        self.saveSettings()