class TektronixScopeWidget(Ui.Ui_Form, QWidget): def __init__(self, parent=None): super(TektronixScopeWidget, self).__init__(parent) self.setupUi(self) self.thread = None self.hdfFile = None self.columns = { 'enabled': 0, 'save': 1, 'coupling': 2, 'mode': 3, 'gain': 4, 'label': 5 } self.plot.addLegend() self.plot.setLabel('left', 'voltage', units='V') self.plot.setLabel('bottom', 'time', units='s') self.activeChannels = [1, 2] pens = 'ybmr' self.curves = [] for ch in self.activeChannels: curve = pg.PlotDataItem(pen=pens[ch], name='Channel %d' % ch) self.plot.addItem(curve) self.curves.append(curve) self.connectToInstrument() self.publisher = ZmqPublisher('TektronixScope', port=PubSub.TektronixScope) def removeAllCurves(self): for curve in self.curves: self.plot.removeItem(curve) del curve self.curves = [] self.plot.plotItem.legend.items = [] def connectToInstrument(self): address = "wisptek2.physics.wisc.edu" print "Connecting to instrument:", address scope = TektronixTds3000(address) print scope.identify() for source in scope.TriggerSources: self.triggerSourceCombo.addItem(source) self.triggerSourceCombo.currentIndexChanged.connect(self.update) self.scope = scope self.thread = ScopeThread(self.scope, self) self.thread.setActiveChannels(self.activeChannels) self.thread.dataReady.connect(self.collectData) self.thread.terminated.connect(self.restartThread) self.thread.start() def collectData(self, channel, t, y): dt = t[1] - t[0] timeStamp = time.time() dataSet = {'t': timeStamp, 'dt': dt} print "Sample rate:", 1. / dt self.publisher.publish(channel, dataSet, arrays={'CH%d' % channel: y}) for i, ch in enumerate(self.activeChannels): if ch == channel: self.curves[i].setData(t, y) def populateUi(self): s = QSettings(OrganizationName, ApplicationName) def saveSettings(self): s = QSettings(OrganizationName, ApplicationName) def restartThread(self): '''This is a hack to get the thread restarted if it stops due to communications error''' print "Restarting" self.thread.wait(1000) del self.thread self.thread = None self.connectToInstrument() def endThread(self): self.thread.stop() self.thread.wait(2000) self.thread = None def closeEvent(self, e): if self.thread is not None: self.endThread() self.saveSettings() super(TektronixScopeWidget, self).closeEvent(e)
class MagnetControlThread(QThread): '''Magnet control thread''' DIODE_I0 = 4.2575E-11 # A DIODE_A = 37.699 # 1/V (=q_q/(k_B*T)) quenchDetected = pyqtSignal() supplyVoltageUpdated = pyqtSignal(float) programmedSupplyVoltageUpdated = pyqtSignal(float) supplyCurrentUpdated = pyqtSignal(float) magnetVoltageUpdated = pyqtSignal(float) diodeVoltageUpdated = pyqtSignal(float) resistiveVoltageUpdated = pyqtSignal(float) resistanceUpdated = pyqtSignal(float) rampRateUpdated = pyqtSignal(float) measurementAvailable = pyqtSignal(float, float, float, float) def __init__(self, magnetSupply, dmm, parent=None): QThread.__init__(self, parent) self.ms = magnetSupply self.dmm = dmm self.interval = 0.2 # Update interval self.dIdtMax = 1./60. # max rate: 1 A/min = 1./60. A/s self.dIdt = 0. self.Rmax = 0.6 # Maximum R for quench detection self.inductance = 30.0 # 30 Henry self.Vmax = 2.8 # Maximum supply voltage self.Imax = 8.5 # Maximum current permitted self._quenched = False self.publisher = ZmqPublisher('MagnetControlThread', PubSub.MagnetControl, self) @property def quenched(self): return self._quenched @property def maximumCurrent(self): return self._Imax @maximumCurrent.setter def maximumCurrent(self, Imax): self._Imax = Imax @property def inductance(self): return self._L @inductance.setter def inductance(self, L): if L > 0: self._L = L else: raise Exception('Inductance must be positive.') def setRampRate(self, dIdt): '''Specify desired ramp rate in A/s.''' if self.quenched: self.dIdt = -0.5*self.dIdtMax # Fast ramp down else: self.dIdt = max(-self.dIdtMax, min(self.dIdtMax, dIdt)) self.rampRateUpdated.emit(self.dIdt) def log(self, t): s = QSettings('WiscXrayAstro', application='ADR3RunInfo') path = str(s.value('runPath', '', type=str)) fileName = os.path.join(path,'MagnetControl_%s.dat' % time.strftime('%Y%m%d')) if not os.path.isfile(fileName): with open(fileName, 'a') as f: header = '#tUnix\tVsupply\tIsupply\tVmagnet\tVsupplyProg\n' f.write(header) text = '%.3f\t%.3f\t%.3f\t%.5g\t%.5f\n' % (t, self.supplyVoltage, self.supplyCurrent, self.magnetVoltage, self.programmedSupplyVoltage) with open(fileName, 'a') as f: f.write(text) def diodeVoltageDrop(self, current): '''Calculate approximate the diode voltage drop for a given current. ''' if current < 0: raise Exception('Current must be positive') Vdiode = log(current/self.DIODE_I0+1)/self.DIODE_A return Vdiode @property def interval(self): return self._interval @interval.setter def interval(self, seconds): self._interval = float(seconds) def stop(self): self.stopRequested = True logger.debug("MagnetControlThread stop requested.") def triggerQuench(self): self._quenched = True logger.warning("Quench detected!") self.quenchDetected.emit() self.setRampRate(0) # This will set the ramp rate to ramp down def measureSupplyCurrent(self): I = self.ms.supplyCurrent() self.supplyCurrent = I self.supplyCurrentUpdated.emit(I) return I def measureSupplyVoltage(self): (Vprogrammed, Vmeasured) = self.ms.supplyVoltages() self.supplyVoltage = Vmeasured self.supplyVoltageUpdated.emit(Vmeasured) self.programmedSupplyVoltage = Vprogrammed self.programmedSupplyVoltageUpdated.emit(Vprogrammed) return Vprogrammed def measureMagnetVoltage(self): logger.info("Measuring magnet voltage") V = self.dmm.voltageDc() logger.info("Vmagnet=%fV" % V) self.magnetVoltage = V self.magnetVoltageUpdated.emit(V) return V def setSuppyVoltage(self, Vnew): if Vnew >= self.Vmax: Vnew = self.Vmax self.supplyLimit = True elif Vnew <= 0: Vnew = 0 self.supplyLimit = True else: self.supplyLimit = False self.programmedSupplyVoltage = self.ms.setSupplyVoltage(Vnew) self.programmedSupplyVoltageUpdated.emit(self.programmedSupplyVoltage) def sleepPrecise(self,tOld): tSleep = int(1E3*(self.interval-time.time()+tOld-0.010)) if tSleep>0.010: self.msleep(tSleep) while(time.time()-tOld < self.interval): pass def query(self, item): return self.publisher.query(item) def run(self): self.stopRequested = False logger.info("Thread starting") currentHistory = History(maxAge=3) dIdtThreshold = 100E-3 # 100mA/s mismatchCount = 0 mismatch = False try: while not self.stopRequested: # First, take all measurements t = time.time() Isupply = self.measureSupplyCurrent() Vsupply = self.measureSupplyVoltage() Vmagnet = self.measureMagnetVoltage() self.measurementAvailable.emit(t, Isupply, Vsupply, Vmagnet) self.publisher.publish('Isupply', Isupply) self.publisher.publish('Vsupply', Vsupply) self.publisher.publish('Vmagnet', Vmagnet) self.publisher.publish('Imagnet', Isupply) self.publisher.publish('dIdt', Vmagnet/self.inductance) # Log all measurements self.log(t) # Check that Vmagnet matches L * dI/dt currentHistory.append(t, Isupply) dIdt = currentHistory.dydt() if abs(dIdt) > dIdtThreshold or abs(Vmagnet) > self.inductance*dIdtThreshold: match = (dIdt / (Vmagnet/self.inductance))-1. if abs(match) > 0.5: logger.warn("Mismatch between dIdt (%.4f A/s) and magnet voltage (%.5f V)." % (dIdt, Vmagnet)) mismatchCount += 1 else: mismatchCount = 0 else: mismatchCount = 0 # Check for quench if Isupply < 0: Isupply = 0 Vdiode = self.diodeVoltageDrop(Isupply) self.diodeVoltageUpdated.emit(Vdiode) V_R = Vsupply - Vmagnet - Vdiode self.resistiveVoltageUpdated.emit(V_R) if Isupply > 0.1: R = V_R / Isupply else: R = float('nan') self.resistanceUpdated.emit(R) if Isupply > 1: if R > self.Rmax: self.triggerQuench() #Compute new parameters VmagnetGoal = self.inductance*self.dIdt if Isupply >= self.Imax and self.dIdt > 0: VmagnetGoal = 0 errorTerm = (VmagnetGoal-Vmagnet) logger.info("Programmed supply voltage %f" % self.programmedSupplyVoltage) if mismatchCount > 5: logger.warn('Mismatch between dIdt and magnet voltage has persisted!') #logger.warn("Mismatch between dIdt and magnet voltage has persisted, ramping down supply!") #mismatch = True if not mismatch: Vnew = Vsupply + errorTerm else: Vnew = Vsupply - 0.1 if Vnew < 0: break logger.info("Vnew=%f V"% Vnew) self.setSuppyVoltage(Vnew) self.sleepPrecise(t) except Exception: logger.warn("Exception:", exc_info=True) logger.info("MagnetControl ending")