From 54e20316c95d5692514f86da4ddd30abd64cbc4a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=BCnter=20Quast?= <guenter.quast@online.de>
Date: Sun, 16 Jun 2024 18:10:00 +0200
Subject: [PATCH] renamed and extended main app: redPdaq.py

---
 redP_mimoCoRB.py            |   2 +-
 redPoscdaq.py => redPdaq.py | 492 +++++++++++++++++++++++++++++++++---
 2 files changed, 460 insertions(+), 34 deletions(-)
 rename redPoscdaq.py => redPdaq.py (66%)

diff --git a/redP_mimoCoRB.py b/redP_mimoCoRB.py
index 46e65ad..c729eea 100644
--- a/redP_mimoCoRB.py
+++ b/redP_mimoCoRB.py
@@ -7,7 +7,7 @@ via callback of function data_sink() in class redP_mimoCoRB.
 
 import time
 import sys
-import redPoscdaq as rp
+import redPdaq as rp
 from mimocorb.buffer_control import rbPut
 
 class redP_mimocorb():            
diff --git a/redPoscdaq.py b/redPdaq.py
similarity index 66%
rename from redPoscdaq.py
rename to redPdaq.py
index 73513ec..6bcbfba 100755
--- a/redPoscdaq.py
+++ b/redPdaq.py
@@ -3,15 +3,17 @@
     application running on a RedPitaya FPGA board
 
     Contains a button to run the oscilloscope in daq mode, i.e. it is restarted 
-    continously. Triggered wave forms can be exported via calling a callback 
-    function stored in a numpy binary file in .npy format.
+    continously. If defined, data is exported via calling a callback function. 
+    Optionally, triggered waveforms can be stored a numpy binary file 
+    (.npy format).
 
-    This code is derived from mcpha.py by Pavel Demin and 
-    compatible with release 20240204 of the alpine linux image
+    Code derived from mcpha.py by Pavel Demin  
+
+    This code is compatible with release 20240204 of the alpine linux image
     https://github.com/pavel-demin/red-pitaya-notes/releases/tag/20240204
 """
 
-script_name = 'redPoscidaq.py'
+script_name = 'redPdaq.py'
 
 # Communication with server process is achieved via command codes:
 #   command(code, number, data)  #    number typically is channel number
@@ -22,7 +24,7 @@ script_name = 'redPoscidaq.py'
 # code 11:  read status
 # code 13:  set trigger source chan 1/2
 # code 14:  set trigger slope rise/fall
-# code 15:  set trigger mode norm/auto
+# code 15:  set trigger mode norm/autoUi_HstDisplay, QWidget = loadUiType("mcpha_hst.ui")
 # code 16:  set trigger level in ADC counts
 # code 17:  set number of pre-trigger samples
 # code 18:  set number of total samples 
@@ -80,6 +82,7 @@ plt.style.use(_style)
     
 Ui_MCPHA, QMainWindow = loadUiType("rpControl.ui")
 Ui_LogDisplay, QWidget = loadUiType("mcpha_log.ui")
+Ui_HstDisplay, QWidget = loadUiType("mcpha_hst.ui")
 Ui_OscDisplay, QWidget = loadUiType("mcpha_daq.ui")
 Ui_GenDisplay, QWidget = loadUiType("mcpha_gen.ui")
 
@@ -116,25 +119,38 @@ class rpControl(QMainWindow, Ui_MCPHA):
         self.confd = {} if conf_dict is None else conf_dict
         self.parse_confd()
         # get configuration from command line
-        if os.path.split(sys.argv[0])[1]=='redPoscdaq.py':
+        if os.path.split(sys.argv[0])[1] == script_name:
+             self.interactive = True
              self.parse_args()
+        else:
+             self.interactive = False            
         self.callback = True if callback is not None else False
          
         # set physical units (for axis labels)
         self.get_physical_units()
         self.setupUi(self)
         # initialize variables
-        self.osc_ready = False
         self.idle = True
+        self.hst_waiting = [False for i in range(2)]
+        self.osc_ready = False
         self.osc_waiting = False
+        self.hst_reset = 0 
         self.state = 0
         self.status = np.zeros(9, np.uint32)
         self.timers = self.status[:4].view(np.uint64)
         # create tabs
         self.log = LogDisplay()
+        if self.interactive:
+            self.hst1 = HstDisplay(self, self.log, 0)
+            self.hst2 = HstDisplay(self, self.log, 1)
+        else:
+            self.hst1 = None
+            self.hst2 = None
         self.osc_daq = OscDAQ(self, self.log)        
         self.gen = GenDisplay(self, self.log)
         self.tabindex_log = self.tabWidget.addTab(self.log, "Messages")
+        self.tabWidget.addTab(self.hst1, "Spectrum Channel 1")
+        self.tabWidget.addTab(self.hst2, "Spectrum Channel 2")
         self.tabindex_osc = self.tabWidget.addTab(self.osc_daq, "Oscilloscope")
         self.tabindex_gen = self.tabWidget.addTab(self.gen, "Pulse generator")
         # configure controls
@@ -163,7 +179,8 @@ class rpControl(QMainWindow, Ui_MCPHA):
         self.startTimer = QTimer(self)  # timer for network connectin
         self.startTimer.timeout.connect(self.start_timeout)
         self.readTimer = QTimer(self)   # for readout 
-        self.readTimer.timeout.connect(self.update_oscData)
+        # self.readTimer.timeout.connect(self.update_oscDisplay)
+        self.readTimer.timeout.connect(self.read_timeout)
 
         # transfer command-line arguments to gui
         self.osc_daq.levelValue.setValue(self.trigger_level)
@@ -243,7 +260,6 @@ class rpControl(QMainWindow, Ui_MCPHA):
         if self.filename:
             # data recording with npy_append_array()
             import_npy_append_array()
-
         
     def get_physical_units(self):
         """get physical units corresponding to ADC units and channel numbers
@@ -299,11 +315,13 @@ class rpControl(QMainWindow, Ui_MCPHA):
         self.osc_reset = False
         self.osc_start = False
         self.state = 0
+        self.hst_reset = 0
+        self.hst_waiting = [False for i in range(2)]
         self.set_rate(self.rateValue.currentIndex())
         self.set_negator(0, self.neg1Check.isChecked())
         self.set_negator(1, self.neg2Check.isChecked())
         #
-        # finally, start readout timer calling update_oscData()
+        # finally, start readout timer calling update_oscData() read_timeout()
         self.readTimer.start(self.readInterval)
 
        #### start oscilloscope in DAQ mode
@@ -325,36 +343,66 @@ class rpControl(QMainWindow, Ui_MCPHA):
             view[:] = np.frombuffer(self.socket.read(size), np.uint8)
             return True
 
-    def update_oscData(self):
-        """get new data from RP and update oscilloscope display (triggered by QTimer:readTimer)
+    def read_timeout(self):
+        """data transfer from RP,  triggered by QTimer readTimer
         """
-        if not self.osc_waiting:
-            return
+        # send reset commands for histograms
+        if self.hst_reset & 1:
+            self.command(0, 0, 0)  # hst 1
+        if self.hst_reset & 2:
+            self.command(0, 1, 0)  # hst 2
+        if self.hst_reset & 4:
+            self.command(1, 0, 0)  # reset timer 1
+        if self.hst_reset & 8:
+            self.command(1, 1, 0)  # reset timer 2
+        self.hst_reset = 0
         if self.osc_reset:
             self.reset_osc()
         if self.osc_start:
             self.start_osc()
-        # check status
-        self.read_status()
-        if not self.read_data(self.status):
-            self.log.print("failed to read status")
-            return
-        if not self.status[8] & 1:
-            self.command(20, 0, 0)  # read oscilloscope data 
-            if self.read_data(self.osc_daq.buffer):
-                self.osc_daq.update_osci_display()
-                self.mark_reset_osc()
-                self.mark_start_osc()
-            else:
-                self.log.print("failed to read oscilloscope data")
+        # read histogram and oscilloscope data and update displays if not in daq mode
+        if not self.daq_waiting: 
+            self.command(11, 0, 0)  # read status
+            if not self.read_data(self.status):
                 return
+            # spectrum ch1
+            if self.hst_waiting[0]:
+                self.command(12, 0, 0)  # code 12: read histogram
+                if self.read_data(self.hst1.buffer):
+                    self.hst1.update(self.timers[0], False)
+                else:
+                    return
+            # spectrum ch2
+            if self.hst_waiting[1]: 
+                self.command(12, 1, 0)
+                if self.read_data(self.hst2.buffer):
+                    self.hst2.update(self.timers[1], False)
+                else:
+                    return
+            # oscilloscope
+            if self.osc_waiting and not self.status[8] & 1:
+                self.command(20, 0, 0)  # read oscilloscope data 
+                if self.read_data(self.osc_daq.buffer):
+                    self.osc_daq.update_osci_display()
+                    self.mark_reset_osc()
+                    self.mark_start_osc()
+                else:
+                    self.log.print("failed to read oscilloscope data")
+                    return 1
+                
 
     def run_oscDaq(self):
-        """continuous data transfer from RedPitaya
+        """continuous fast data transfer from RedPitaya via mcpha oscilloscope
         """
         # depends on
         #   self.start_daq()
         #   osc.process_data()
+        
+        # presently, not compatible with historgram mode, so pause
+        if self.hst_waiting[0]:
+            self.hst1.pause()
+        if self.hst_waiting[1]:
+            self.hst2.pause()
         # initialize trigger mode etc. 
         self.osc_daq.start_daq()
         #
@@ -379,6 +427,26 @@ class rpControl(QMainWindow, Ui_MCPHA):
                 else:
                     self.log.print("failed to read oscilloscope data")
                     return        
+
+    def reset_hst(self, number):
+        self.hst_reset |= 1 << number
+
+    def reset_timer(self, number):
+        self.hst_reset |= 4 << number
+
+    def set_pha_delay(self, number, value):
+        self.command(6, number, value)
+
+    def set_pha_thresholds(self, number, min, max):
+        self.command(7, number, min)
+        self.command(8, number, max)
+
+    def set_timer(self, number, value):
+        self.command(9, number, value)
+
+    def set_timer_mode(self, number, value):
+        self.command(10, number, value)
+        self.hst_waiting[number] = value
             
     def mark_start_osc(self):
         self.osc_start = True
@@ -389,7 +457,7 @@ class rpControl(QMainWindow, Ui_MCPHA):
 
     def reset_osc(self):        
         self.command(2, 0, 0)  
-        self.osc_reset=False
+        self.osc_reset = False
 
     def start_osc(self):        
         self.command(19, 0, 0)    
@@ -461,6 +529,355 @@ class LogDisplay(QWidget, Ui_LogDisplay):
     def print(self, text):
         self.logViewer.appendPlainText(text)
 
+class HstDisplay(QWidget, Ui_HstDisplay):
+    def __init__(self, rpControl, log, number):
+        super(HstDisplay, self).__init__()
+        self.setupUi(self)
+        # initialize variables
+        self.rpControl = rpControl
+        self.log = log
+        self.number = number
+        self.sum = 0
+        self.time = np.uint64([75e8, 0])
+        self.factor = 1
+        self.bins = 4096
+        self.min = 0
+        self.max = self.bins - 1 
+        self.buffer = np.zeros(self.bins, np.uint32)
+        if number == 0:
+            self.color = "#FFAA00"
+        else:
+            self.color = "#00CCCC"
+        # create figure
+        self.figure = Figure()
+        if sys.platform != "win32":
+            self.figure.set_facecolor("none")
+        # gq self.figure.subplots_adjust(left=0.18, bottom=0.08, right=0.98, top=0.95)
+        self.figure.subplots_adjust(left=0.10, bottom=0.08, right=0.98, top=0.92)
+        self.canvas = FigureCanvas(self.figure)
+        self.plotLayout.addWidget(self.canvas)
+        self.ax = self.figure.add_subplot(111)
+        self.ax.grid()
+        self.ax.set_ylabel("counts")
+        self.xunit = "[{:.3f} mV / channel]".format(1000/4096 * self.factor)
+        self.ax.set_xlabel("channel number " + self.xunit)
+#        self.ax.set_xlabel("channel number")
+        # !gq
+        self.ax_x2=self.ax.secondary_xaxis('top',
+                functions=(self.adc2mV, self.mV2adc))
+        self.ax_x2.set_xlabel("Voltage [mV]", color='grey')
+        # !gq end
+        x = np.arange(self.bins)
+        (self.curve,) = self.ax.plot(x, self.buffer, drawstyle="steps-mid", color=self.color)
+        self.roi = [0, self.max]
+        self.line = [None, None]
+        self.active = [False, False]
+        self.releaser = [None, None]
+        # marker lines for roi
+        self.line[0] = self.ax.axvline(self.min, picker=True, pickradius=5)
+        self.line[1] = self.ax.axvline(self.max, picker=True, pickradius=5)
+        # create navigation toolbar
+        self.toolbar = NavigationToolbar(self.canvas, None, False)
+        self.toolbar.layout().setSpacing(6)
+        # remove subplots action
+        actions = self.toolbar.actions()
+        self.toolbar.removeAction(actions[6])
+        self.toolbar.removeAction(actions[7])
+        self.logCheck = QCheckBox("log scale")
+        self.logCheck.setChecked(False)
+        self.binsLabel = QLabel("rebin factor")
+        self.binsValue = QComboBox()
+        self.binsValue.addItems(["1", "2", "4", "8"])
+        self.binsValue.setEditable(True)
+        self.binsValue.lineEdit().setReadOnly(True)
+        self.binsValue.lineEdit().setAlignment(Qt.AlignRight)
+        for i in range(self.binsValue.count()):
+            self.binsValue.setItemData(i, Qt.AlignRight, Qt.TextAlignmentRole)
+        self.toolbar.addSeparator()
+        self.toolbar.addWidget(self.logCheck)
+        self.toolbar.addSeparator()
+        self.toolbar.addWidget(self.binsLabel)
+        self.toolbar.addWidget(self.binsValue)
+        self.plotLayout.addWidget(self.toolbar)
+        # configure controls
+        actions[0].triggered.disconnect()
+        actions[0].triggered.connect(self.home)
+        self.logCheck.toggled.connect(self.set_scale)
+        self.binsValue.currentIndexChanged.connect(self.set_bins)
+        self.thrsCheck.toggled.connect(self.set_thresholds)
+        self.startButton.clicked.connect(self.start)
+        self.resetButton.clicked.connect(self.reset)
+        self.saveButton.clicked.connect(self.save)
+        self.loadButton.clicked.connect(self.load)
+        self.canvas.mpl_connect("motion_notify_event", self.on_motion)
+        self.canvas.mpl_connect("pick_event", self.on_pick)
+        # update controls
+        self.set_thresholds(self.thrsCheck.isChecked())
+        self.set_time(self.time[0])
+        self.set_scale(self.logCheck.isChecked())
+
+    # !gq helper functions to convert adc counts to physical units    
+    def adc2mV(self, c):
+        """convert adc-count to Voltage in mV
+        """
+        return c * self.rpControl.adc_unit * self.factor
+
+    def mV2adc(self, v):
+        """convert voltage in mV to adc-count 
+        """
+        return v / (self.rpControl.adc_unit * self.factor)
+    # !gq end helper
+        
+    def start(self):
+        if self.rpControl.idle:
+            return
+        self.set_thresholds(self.thrsCheck.isChecked())
+        self.set_enabled(False)
+        h = self.hoursValue.value()
+        m = self.minutesValue.value()
+        s = self.secondsValue.value()
+        value = (h * 3600000 + m * 60000 + s * 1000) * 125000
+        self.sum = 0
+        self.time[:] = [value, 0]
+        self.rpControl.reset_timer(self.number)
+        self.rpControl.set_pha_delay(self.number, 100)
+        self.rpControl.set_pha_thresholds(self.number, self.min, self.max)
+        self.rpControl.set_timer(self.number, value)
+#
+        self.resume()
+
+    def pause(self):
+        self.rpControl.set_timer_mode(self.number, 0)
+        self.startButton.setText("Resume")
+        self.startButton.clicked.disconnect()
+        self.startButton.clicked.connect(self.resume)
+        self.log.print("timer %d stopped" % (self.number + 1))
+
+    def resume(self):
+        self.rpControl.set_timer_mode(self.number, 1)
+        self.startButton.setText("Pause")
+        self.startButton.clicked.disconnect()
+        self.startButton.clicked.connect(self.pause)
+        self.log.print("timer %d started" % (self.number + 1))
+
+    def stop(self):
+        self.rpControl.set_timer_mode(self.number, 0)
+        self.set_enabled(True)
+        self.set_time(self.time[0])
+        self.startButton.setText("Start")
+        self.startButton.clicked.disconnect()
+        self.startButton.clicked.connect(self.start)
+        self.log.print("timer %d stopped" % (self.number + 1))
+
+    def reset(self):
+        if self.rpControl.idle:
+            return
+        self.stop()
+        self.rpControl.reset_hst(self.number)
+        self.rpControl.reset_timer(self.number)
+        self.totalValue.setText("%.2e" % 0)
+        self.instValue.setText("%.2e" % 0)
+        self.avgValue.setText("%.2e" % 0)
+        self.buffer[:] = np.zeros(self.bins, np.uint32)
+        self.update_plot()
+        self.update_roi()
+
+    def set_enabled(self, value):
+        if value:
+            self.set_thresholds(self.thrsCheck.isChecked())
+        else:
+            self.minValue.setEnabled(False)
+            self.maxValue.setEnabled(False)
+        self.thrsCheck.setEnabled(value)
+        self.hoursValue.setEnabled(value)
+        self.minutesValue.setEnabled(value)
+        self.secondsValue.setEnabled(value)
+
+    def home(self):
+        self.set_scale(self.logCheck.isChecked())
+
+    def set_xplot_range(self):
+        """set x-range for histogram plot
+        """
+        mn = self.min // self.factor
+        mx = self.max // self.factor
+        xsize = mx - mn
+        self.ax.set_xlim(mn - 0.02*xsize, mx*1.02)
+        self.ax.relim()
+        self.ax.autoscale_view(scalex=True, scaley=True)
+            
+    def set_scale(self, checked):
+        self.toolbar.home()
+        self.toolbar.update()
+        if checked:
+            self.ax.set_ylim(1, 1e10)
+            self.ax.set_yscale("log")
+        else:
+            self.ax.set_ylim(auto=True)
+            self.ax.set_yscale("linear")
+    # !gq size = self.bins // self.factor
+    #    self.ax.set_xlim(-0.05 * size, size * 1.05)
+        self.set_xplot_range()
+        self.canvas.draw()
+
+    def set_bins(self, value):
+        factor = 1 << value
+        self.factor = factor
+        bins = self.bins // self.factor
+        self.xunit = "[{:.3f} mV / channel]".format(1000/4096 * self.factor)
+        self.ax.set_xlabel("channel number " + self.xunit)
+        x = np.arange(bins)
+        y = self.buffer.reshape(-1, self.factor).sum(-1)
+        self.curve.set_xdata(x)
+        self.curve.set_ydata(y)
+        self.set_scale(self.logCheck.isChecked())
+    # gq update roi
+        self.roi[0] = self.min
+        self.roi[1] = self.max
+        self.update_roi()
+
+    def set_thresholds(self, checked):
+        self.minValue.setEnabled(checked)
+        self.maxValue.setEnabled(checked)
+        if checked:
+            self.min = self.minValue.value()
+            self.max = self.maxValue.value()
+        else:
+            self.min = 0
+            self.max = self.bins - 1
+
+        # !gq update xplot range and roi
+        self.set_xplot_range()
+        self.roi[0] = self.min
+        self.roi[1] = self.max
+        self.update_roi()
+
+    def set_time(self, value):
+        value = value // 125000
+        h, mod = divmod(value, 3600000)
+        m, mod = divmod(mod, 60000)
+        s = mod / 1000
+        self.hoursValue.setValue(int(h))
+        self.minutesValue.setValue(int(m))
+        self.secondsValue.setValue(s)
+
+    def update(self, value, sync):
+        self.update_rate(value)
+        if not sync:
+            self.update_time(value)
+        self.update_plot()
+        self.update_roi()
+
+    def update_rate(self, value):
+        sum = self.buffer.sum()
+        self.totalValue.setText("%.2e" % sum)
+        if value > self.time[1]:
+            rate = (sum - self.sum) / (value - self.time[1]) * 125e6
+            self.instValue.setText("%.2e" % rate)
+        if value > 0:
+            rate = sum / value * 125e6
+            self.avgValue.setText("%.2e" % rate)
+        self.sum = sum
+        self.time[1] = value
+
+    def update_time(self, value):
+        if value < self.time[0]:
+            self.set_time(self.time[0] - value)
+        else:
+            self.stop()
+
+    def update_plot(self):
+        y = self.buffer.reshape(-1, self.factor).sum(-1)
+        self.curve.set_ydata(y)
+        self.ax.relim()
+        self.ax.autoscale_view(scalex=False, scaley=True)
+        self.canvas.draw()
+
+    def update_roi(self):
+        y = self.buffer.reshape(-1, self.factor).sum(-1)
+        x0 = self.roi[0] // self.factor
+        x1 = self.roi[1] // self.factor
+        roi = y[x0 : x1 + 1]
+        y0 = roi[0]
+        y1 = roi[-1]
+        tot = roi.sum()
+        bkg = (x1 + 1 - x0) * (y0 + y1) / 2.0
+        self.roistartValue.setText("%d" % x0)
+        self.roiendValue.setText("%d" % x1)
+        self.roitotValue.setText("%.2e" % tot)
+        self.roibkgValue.setText("%.2e" % bkg)
+        self.line[0].set_xdata([x0, x0])
+        self.line[1].set_xdata([x1, x1])
+        self.canvas.draw_idle()
+
+    def on_motion(self, event):
+        if event.inaxes != self.ax:
+            return
+        x = int(event.xdata + 0.5)
+        if x < 0:
+            x = 0
+        if x >= self.bins // self.factor:
+            x = self.bins // self.factor - 1
+        y = self.curve.get_ydata(True)[x]
+        self.numberValue.setText("%d" % x)
+        self.entriesValue.setText("%d" % y)
+        delta = 40
+        if self.active[0]:
+            x0 = x * self.factor
+            if x0 > self.roi[1] - delta:
+                self.roi[0] = self.roi[1] - delta
+            else:
+                self.roi[0] = x0
+            self.update_roi()
+        if self.active[1]:
+            x1 = x * self.factor
+            if x1 < self.roi[0] + delta:
+                self.roi[1] = self.roi[0] + delta
+            else:
+                self.roi[1] = x1
+            self.update_roi()
+
+    def on_pick(self, event):
+        for i in range(2):
+            if event.artist == self.line[i]:
+                self.active[i] = True
+                self.releaser[i] = self.canvas.mpl_connect("button_release_event", partial(self.on_release, i))
+
+    def on_release(self, i, event):
+        self.active[i] = False
+        self.canvas.mpl_disconnect(self.releaser[i])
+
+    def save(self):
+        try:
+            dialog = QFileDialog(self, "Save hst file", path, "*.hst")
+            dialog.setDefaultSuffix("hst")
+            timestamp = time.strftime("%Y%m%d-%H%M%S")
+            name = "histogram-%s.hst" % timestamp
+            dialog.selectFile(name)
+            dialog.setAcceptMode(QFileDialog.AcceptSave)
+            if dialog.exec() == QDialog.Accepted:
+                name = dialog.selectedFiles()
+                np.savetxt(name[0],
+                           self.buffer,
+                           fmt="%u",
+                           header ='mcpha spectrum ' + timestamp + '\n counts per channel',
+                           newline=os.linesep)
+                self.log.print("histogram %d saved to file %s" % ((self.number + 1), name[0]))
+        except:
+            self.log.print("error: %s" % sys.exc_info()[1])
+
+    def load(self):
+        try:
+            dialog = QFileDialog(self, "Load hst file", path, "*.hst")
+            dialog.setDefaultSuffix("hst")
+            dialog.setAcceptMode(QFileDialog.AcceptOpen)
+            if dialog.exec() == QDialog.Accepted:
+                name = dialog.selectedFiles()
+                self.buffer[:] = np.loadtxt(name[0], np.uint32)
+                self.update_plot()
+        except:
+            self.log.print("error: %s" % sys.exc_info()[1])
+        
 
 class OscDAQ(QWidget, Ui_OscDisplay):
     """Oscilloscope display with daq mode:
@@ -598,11 +1015,15 @@ class OscDAQ(QWidget, Ui_OscDisplay):
         self.rpControl.set_osc_tot(self.l_tot)
 
     def set_gui4start(self):
-        self.startButton.setText("Start")
+        self.startButton.setText("Start monitor")
         self.startButton.setStyleSheet("")
         self.startButton.clicked.disconnect()
         self.startButton.clicked.connect(self.start)
         self.startDAQButton.setEnabled(True)
+        if self.rpControl.hst1 is not None:
+            self.rpControl.hst1.startButton.setEnabled(True)
+        if self.rpControl.hst2 is not None:
+            self.rpControl.hst2.startButton.setEnabled(True)
 
     def set_gui4stop(self):
         # set start and stop buttons
@@ -611,7 +1032,7 @@ class OscDAQ(QWidget, Ui_OscDisplay):
         self.startButton.clicked.disconnect()
         self.startButton.clicked.connect(self.stop)
         self.startDAQButton.setEnabled(False)
-        
+
     def start(self):
         """start oscilloscope display
         """
@@ -643,6 +1064,11 @@ class OscDAQ(QWidget, Ui_OscDisplay):
         # set-up trigger and GUI Buttons
         self.setup_trigger()
         self.set_gui4stop()
+        # disable spectrum update
+        if self.rpControl.hst1 is not None:
+            self.rpControl.hst1.startButton.setEnabled(False)
+        if self.rpControl.hst2 is not None:
+            self.rpControl.hst2.startButton.setEnabled(False)
         self.rpControl.daq_waiting = True
         self.log.print("daq started")
        
@@ -671,7 +1097,7 @@ class OscDAQ(QWidget, Ui_OscDisplay):
             with NpyAppendArray(self.filename) as npa:
                 npa.append( np.array([self.data]) )
         #        
-        # output status and update scope display once per second
+        # output status and update scope display once per readInterval
         if 1000*self.dT >= self.rpControl.readInterval:
             dN = self.NTrig - self.Nprev
             r = dN/self.dT
-- 
GitLab