예제 #1
0
class ScalarLiveViewPanel(wx.Panel):
	"""
	A panel to display a live view plot of a scalar resource.
	"""

	def __init__(self, parent, global_store, *args, **kwargs):
		wx.Panel.__init__(self, parent, *args, **kwargs)

		self.global_store = global_store
		self._measurement_resource_name = None

		# Defaults.
		self.plot_settings = PlotSettings()
		self.unit_conversion = 0

		self.enabled = False
		self.capturing_data = False
		self.restart_live_view = False
		self.resource_backup = None

		# This lock blocks the acquisition thread from acquiring.
		self.running_lock = Lock()

		# Initialize recorded values.
		self.init_values()

		# Plot and toolbar.
		display_box = wx.BoxSizer(wx.VERTICAL)

		## Plot.
		if plot_available:
			self.plot = TwoDimensionalPlot(self, color='blue')
			display_box.Add(self.plot.control, proportion=1, flag=wx.EXPAND)

			self.plot.x_label = 'Time (s)'
		else:
			display_box.Add((500, -1), proportion=1, flag=wx.EXPAND)

		## Controls.
		if plot_available:
			controls_box = wx.BoxSizer(wx.HORIZONTAL)
			display_box.Add(controls_box, flag=wx.CENTER|wx.ALL, border=5)

			### Numeric display.
			numeric_display_static_box = wx.StaticBox(self, label='Reading')
			numeric_display_box = wx.StaticBoxSizer(numeric_display_static_box, wx.HORIZONTAL)
			controls_box.Add(numeric_display_box, flag=wx.CENTER)

			self.numeric_display = wx.TextCtrl(self, size=(100, -1), style=wx.TE_READONLY)
			self.numeric_display.BackgroundColour = wx.LIGHT_GREY
			numeric_display_box.Add(self.numeric_display)

			### Capture.
			capture_static_box = wx.StaticBox(self, label='Control')
			capture_box = wx.StaticBoxSizer(capture_static_box)
			controls_box.Add(capture_box, flag=wx.CENTER|wx.LEFT, border=10)

			self.run_button = wx.Button(self, label='Run')
			self.Bind(wx.EVT_BUTTON, self.OnRun, self.run_button)
			capture_box.Add(self.run_button, flag=wx.CENTER)

			self.pause_button = wx.Button(self, label='Pause')
			self.Bind(wx.EVT_BUTTON, self.OnPause, self.pause_button)
			capture_box.Add(self.pause_button, flag=wx.CENTER)

			self.reset_button = wx.Button(self, label='Reset')
			self.Bind(wx.EVT_BUTTON, self.OnReset, self.reset_button)
			capture_box.Add(self.reset_button, flag=wx.CENTER|wx.LEFT, border=10)

			### Settings.
			settings_static_box = wx.StaticBox(self, label='Settings')
			settings_box = wx.StaticBoxSizer(settings_static_box, wx.HORIZONTAL)
			controls_box.Add(settings_box, flag=wx.CENTER|wx.LEFT, border=10)

			self.plot_settings_button = wx.Button(self, label='Plot...')
			self.Bind(wx.EVT_BUTTON, self.OnPlotSettings, self.plot_settings_button)
			settings_box.Add(self.plot_settings_button, flag=wx.CENTER)

		self.SetSizer(display_box)

		# Acquisition thread.
		callback = functools.partial(wx.CallAfter, self.add_value)
		self.acq_thread = AcquisitionThread(self.plot_settings.delay, callback,
				running_lock=self.running_lock)
		self.acq_thread.daemon = True
		self.acq_thread.start()

		# Wait for a resource to begin capturing.
		self.OnPause()
		self.run_button.Disable()

		# Subscriptions.
		pub.subscribe(self.msg_resource, 'resource.added')
		pub.subscribe(self.msg_resource, 'resource.removed')
		pub.subscribe(self.msg_data_capture_start, 'data_capture.start')
		pub.subscribe(self.msg_data_capture_data, 'data_capture.data')
		pub.subscribe(self.msg_data_capture_stop, 'data_capture.stop')

	@property
	def running(self):
		return self.pause_button.Enabled

	@property
	def resource(self):
		return self.acq_thread.resource

	@resource.setter
	def resource(self, value):
		# Ignore unreadable resources.
		if value is not None and not value.readable:
			value = None

		if self.running:
			# Currently running.
			running = True
			self.OnPause()
		else:
			running = False

		self.acq_thread.resource = value

		self.run_button.Enable(value is not None)

		# Resume if applicable.
		if running:
			self.OnRun()

	@property
	def measurement_resource_name(self):
		if self._measurement_resource_name is None:
			return ''
		else:
			return self._measurement_resource_name

	@measurement_resource_name.setter
	def measurement_resource_name(self, value):
		if value:
			self._measurement_resource_name = value
			try:
				self.resource = self.global_store.resources[self._measurement_resource_name]
			except KeyError:
				self.resource = None
		else:
			self._measurement_resource_name = None
			self.resource = None

	def init_values(self):
		"""
		Clear captured values.
		"""

		self._points = numpy.array([])
		self._times = numpy.array([])
		self._values = numpy.array([])

		self.current_value = None

		self.start_time = None

	def update_plot(self):
		"""
		Redraw the plot.
		"""

		if not len(self._points) > 0:
			display_time = [0]
			display_values = [0]
		else:
			if self.plot_settings.time_value == 0: # Time.
				display_time = self._times

				if self.plot_settings.time_mode == 0: # Relative.
					# Calculate the number of seconds passed since each point.
					max_time = self._times[-1]
					display_time = [x - max_time for x in display_time]
				elif self.plot_settings.time_mode == 1: # Absolute.
					display_time = [x - self.start_time for x in display_time]
			elif self.plot_settings.time_value == 1: # Points.
				display_time = self._points

				if self.plot_settings.time_mode == 0: # Relative.
					# Calculate the number of seconds passed since each point.
					max_point = self._points[-1]
					display_time = [x - max_point for x in display_time]

			display_values = [x * 10 ** (self.plot_settings.y_scale + self.unit_conversion) for
					x in self._values]

		if self.plot_settings.update_x:
			self.plot.x_autoscale()
		if self.plot_settings.update_y:
			self.plot.y_autoscale()

		self.plot.x_data, self.plot.y_data = display_time, display_values

	def add_value(self, value):
		"""
		Update the plot with a new value.
		"""

		if not self.plot_settings.enabled:
			return

		# Extract the value of a Quantity.
		try:
			# Label with the base dimensions.
			if self.unit_conversion == 0:
				self.plot.y_label = '({0})'.format(value.original_units)
			value = value.original_value
		except AttributeError:
			pass

		# Update values.
		try:
			self._points = numpy.append(self._points, self._points[-1] + 1)
		except IndexError:
			self._points = numpy.append(self._points, 0)
		cur_time = time.time()
		self._times = numpy.append(self._times, cur_time)
		self._values = numpy.append(self._values, value)

		if self.start_time is None:
			self.start_time = cur_time

		cut_idx = len(self._points) - int(self.plot_settings.num_points)
		if cut_idx > 0:
			self._points = self._points[cut_idx:]
			self._times = self._times[cut_idx:]
			self._values = self._values[cut_idx:]

		# Set number display.
		self.current_value = value * 10 ** (self.plot_settings.y_scale + self.unit_conversion)
		self.numeric_display.Value = '{0:.6g}'.format(self.current_value)

		# Plot.
		self.update_plot()

	def close(self):
		"""
		Perform cleanup.
		"""

		# Unsubscriptions.
		pub.unsubscribe(self.msg_resource, 'resource.added')
		pub.unsubscribe(self.msg_resource, 'resource.removed')
		pub.unsubscribe(self.msg_data_capture_start, 'data_capture.start')
		pub.unsubscribe(self.msg_data_capture_data, 'data_capture.data')
		pub.unsubscribe(self.msg_data_capture_stop, 'data_capture.stop')

		# Ensure the thread exits.
		self.acq_thread.resource = None
		self.acq_thread.done = True
		if not self.running:
			self.running_lock.release()
		self.acq_thread.join()
		del self.acq_thread

	def OnRun(self, evt=None):
		"""
		Let the acquisition thread run.
		"""

		self.run_button.Disable()

		if self.acq_thread.resource is None:
			return

		self.running_lock.release()

		self.pause_button.Enable()

	def OnPause(self, evt=None):
		"""
		Block the acquisition thread.
		"""

		if not self.running:
			return

		self.running_lock.acquire()

		if self.acq_thread.resource is not None:
			self.run_button.Enable()
		self.pause_button.Disable()

	def OnReset(self, evt=None):
		self.init_values()
		self.update_plot()

	def OnPlotSettings(self, evt=None):
		"""
		Open the plot settings dialog.
		"""

		def ok_callback(dlg):
			self.plot_settings = dlg.GetValue()

			if self.plot_settings.units_from and self.plot_settings.units_to:
				try:
					quantity_from = Quantity(1, self.plot_settings.units_from)
					quantity_to = Quantity(1, self.plot_settings.units_to)
				except ValueError as e:
					self.unit_conversion = 0
					MessageDialog(self, str(e), 'Invalid unit').Show()
				else:
					# We don't actually care about the units; just the prefix values.
					self.unit_conversion = math.log(quantity_from.value, 10) - math.log(quantity_to.value, 10)
			else:
				self.unit_conversion = 0

			self.acq_thread.delay = self.plot_settings.delay

			if self.plot_settings.time_value == 0:
				self.plot.x_label = 'Time (s)'
			elif self.plot_settings.time_value == 1:
				self.plot.x_label = 'Points'

			if self.plot_settings.y_scale != 0:
				self.plot.y_label = '/ 10 ^ {0}'.format(self.plot_settings.y_scale)
			else:
				self.plot.y_label = ''

			if self.plot_settings.units_to:
				self.plot.y_label += ' ({0})'.format(self.plot_settings.units_to)

			self.update_plot()

		dlg = PlotSettingsDialog(self, ok_callback)
		dlg.SetValue(self.plot_settings)
		dlg.Show()

	def msg_resource(self, name, value=None):
		if self.measurement_resource_name is not None and name == self.measurement_resource_name:
			self.resource = value

	def msg_data_capture_start(self, name):
		if name == self.measurement_resource_name:
			if self.enabled:
				self.capturing_data = True

				# Keep track of whether to restart the capture afterwards.
				self.restart_live_view = self.running

				# Disable live view.
				self.resource_backup = self.resource
				self.resource = None

	def msg_data_capture_data(self, name, value):
		if name == self.measurement_resource_name:
			if self.capturing_data:
				self.add_value(value)

	def msg_data_capture_stop(self, name):
		if name == self.measurement_resource_name:
			if self.capturing_data:
				self.capturing_data = False

				# Re-enable live view.
				self.resource = self.resource_backup
				self.resource_backup = None

				if self.restart_live_view:
					self.OnRun()
class ScalarLiveViewPanel(wx.Panel):
	"""
	A panel to display a live view plot of a scalar resource.
	"""

	def __init__(self, parent, global_store, *args, **kwargs):
		wx.Panel.__init__(self, parent, *args, **kwargs)

		self.global_store = global_store
		self._measurement_resource_name = None

		# Defaults.
		self.plot_settings = PlotSettings()
		self.unit_conversion = 0

		self.enabled = False
		self.capturing_data = False
		self.restart_live_view = False
		self.resource_backup = None

		# This lock blocks the acquisition thread from acquiring.
		self.running_lock = Lock()

		# Initialize recorded values.
		self.init_values()

		# Plot and toolbar.
		display_box = wx.BoxSizer(wx.VERTICAL)

		## Plot.
		if plot_available:
			self.plot = TwoDimensionalPlot(self, color='blue')
			display_box.Add(self.plot.control, proportion=1, flag=wx.EXPAND)

			self.plot.x_label = 'Time (s)'
		else:
			display_box.Add((500, -1), proportion=1, flag=wx.EXPAND)

		## Controls.
		if plot_available:
			controls_box = wx.BoxSizer(wx.HORIZONTAL)
			display_box.Add(controls_box, flag=wx.CENTER|wx.ALL, border=5)

			### Numeric display.
			numeric_display_static_box = wx.StaticBox(self, label='Reading')
			numeric_display_box = wx.StaticBoxSizer(numeric_display_static_box, wx.HORIZONTAL)
			controls_box.Add(numeric_display_box, flag=wx.CENTER)

			self.numeric_display = wx.TextCtrl(self, size=(100, -1), style=wx.TE_READONLY)
			self.numeric_display.BackgroundColour = wx.LIGHT_GREY
			numeric_display_box.Add(self.numeric_display)

			### Capture.
			capture_static_box = wx.StaticBox(self, label='Control')
			capture_box = wx.StaticBoxSizer(capture_static_box)
			controls_box.Add(capture_box, flag=wx.CENTER|wx.LEFT, border=10)

			self.run_button = wx.Button(self, label='Run')
			self.Bind(wx.EVT_BUTTON, self.OnRun, self.run_button)
			capture_box.Add(self.run_button, flag=wx.CENTER)

			self.pause_button = wx.Button(self, label='Pause')
			self.Bind(wx.EVT_BUTTON, self.OnPause, self.pause_button)
			capture_box.Add(self.pause_button, flag=wx.CENTER)

			self.reset_button = wx.Button(self, label='Reset')
			self.Bind(wx.EVT_BUTTON, self.OnReset, self.reset_button)
			capture_box.Add(self.reset_button, flag=wx.CENTER|wx.LEFT, border=10)

			### Settings.
			settings_static_box = wx.StaticBox(self, label='Settings')
			settings_box = wx.StaticBoxSizer(settings_static_box, wx.HORIZONTAL)
			controls_box.Add(settings_box, flag=wx.CENTER|wx.LEFT, border=10)

			self.plot_settings_button = wx.Button(self, label='Plot...')
			self.Bind(wx.EVT_BUTTON, self.OnPlotSettings, self.plot_settings_button)
			settings_box.Add(self.plot_settings_button, flag=wx.CENTER)

		self.SetSizer(display_box)

		# Acquisition thread.
		callback = functools.partial(wx.CallAfter, self.add_value)
		self.acq_thread = AcquisitionThread(self.plot_settings.delay, callback,
				running_lock=self.running_lock)
		self.acq_thread.daemon = True
		self.acq_thread.start()

		# Wait for a resource to begin capturing.
		self.OnPause()
		self.run_button.Disable()

		# Subscriptions.
		pub.subscribe(self.msg_resource, 'resource.added')
		pub.subscribe(self.msg_resource, 'resource.removed')
		pub.subscribe(self.msg_data_capture_start, 'data_capture.start')
		pub.subscribe(self.msg_data_capture_data, 'data_capture.data')
		pub.subscribe(self.msg_data_capture_stop, 'data_capture.stop')

	@property
	def running(self):
		return self.pause_button.Enabled

	@property
	def resource(self):
		return self.acq_thread.resource

	@resource.setter
	def resource(self, value):
		# Ignore unreadable resources.
		if value is not None and not value.readable:
			value = None

		if self.running:
			# Currently running.
			running = True
			self.OnPause()
		else:
			running = False

		self.acq_thread.resource = value

		self.run_button.Enable(value is not None)

		# Resume if applicable.
		if running:
			self.OnRun()

	@property
	def measurement_resource_name(self):
		if self._measurement_resource_name is None:
			return ''
		else:
			return self._measurement_resource_name

	@measurement_resource_name.setter
	def measurement_resource_name(self, value):
		if value:
			self._measurement_resource_name = value
			try:
				self.resource = self.global_store.resources[self._measurement_resource_name]
			except KeyError:
				self.resource = None
		else:
			self._measurement_resource_name = None
			self.resource = None

	def init_values(self):
		"""
		Clear captured values.
		"""

		self._points = numpy.array([])
		self._times = numpy.array([])
		self._values = numpy.array([])

		self.current_value = None

		self.start_time = None

	def update_plot(self):
		"""
		Redraw the plot.
		"""

		if not len(self._points) > 0:
			display_time = [0]
			display_values = [0]
		else:
			if self.plot_settings.time_value == 0: # Time.
				display_time = self._times

				if self.plot_settings.time_mode == 0: # Relative.
					# Calculate the number of seconds passed since each point.
					max_time = self._times[-1]
					display_time = [x - max_time for x in display_time]
				elif self.plot_settings.time_mode == 1: # Absolute.
					display_time = [x - self.start_time for x in display_time]
			elif self.plot_settings.time_value == 1: # Points.
				display_time = self._points

				if self.plot_settings.time_mode == 0: # Relative.
					# Calculate the number of seconds passed since each point.
					max_point = self._points[-1]
					display_time = [x - max_point for x in display_time]

			display_values = [x * 10 ** (self.plot_settings.y_scale + self.unit_conversion) for
					x in self._values]

		if self.plot_settings.update_x:
			self.plot.x_autoscale()
		if self.plot_settings.update_y:
			self.plot.y_autoscale()

		self.plot.x_data, self.plot.y_data = display_time, display_values

	def add_value(self, value):
		"""
		Update the plot with a new value.
		"""

		if not self.plot_settings.enabled:
			return

		# Extract the value of a Quantity.
		try:
			# Label with the base dimensions.
			if self.unit_conversion == 0:
				self.plot.y_label = '({0})'.format(value.original_units)
			value = value.original_value
		except AttributeError:
			pass

		# Update values.
		try:
			self._points = numpy.append(self._points, self._points[-1] + 1)
		except IndexError:
			self._points = numpy.append(self._points, 0)
		cur_time = time.time()
		self._times = numpy.append(self._times, cur_time)
		self._values = numpy.append(self._values, value)

		if self.start_time is None:
			self.start_time = cur_time

		cut_idx = len(self._points) - int(self.plot_settings.num_points)
		if cut_idx > 0:
			self._points = self._points[cut_idx:]
			self._times = self._times[cut_idx:]
			self._values = self._values[cut_idx:]

		# Set number display.
		self.current_value = value * 10 ** (self.plot_settings.y_scale + self.unit_conversion)
		self.numeric_display.Value = '{0:.6g}'.format(self.current_value)

		# Plot.
		self.update_plot()

	def close(self):
		"""
		Perform cleanup.
		"""

		# Unsubscriptions.
		pub.unsubscribe(self.msg_resource, 'resource.added')
		pub.unsubscribe(self.msg_resource, 'resource.removed')
		pub.unsubscribe(self.msg_data_capture_start, 'data_capture.start')
		pub.unsubscribe(self.msg_data_capture_data, 'data_capture.data')
		pub.unsubscribe(self.msg_data_capture_stop, 'data_capture.stop')

		# Ensure the thread exits.
		self.acq_thread.resource = None
		self.acq_thread.done = True
		if not self.running:
			self.running_lock.release()
		self.acq_thread.join()
		del self.acq_thread

	def OnRun(self, evt=None):
		"""
		Let the acquisition thread run.
		"""

		self.run_button.Disable()

		if self.acq_thread.resource is None:
			return

		self.running_lock.release()

		self.pause_button.Enable()

	def OnPause(self, evt=None):
		"""
		Block the acquisition thread.
		"""

		if not self.running:
			return

		self.running_lock.acquire()

		if self.acq_thread.resource is not None:
			self.run_button.Enable()
		self.pause_button.Disable()

	def OnReset(self, evt=None):
		self.init_values()
		self.update_plot()

	def OnPlotSettings(self, evt=None):
		"""
		Open the plot settings dialog.
		"""

		def ok_callback(dlg):
			self.plot_settings = dlg.GetValue()

			if self.plot_settings.units_from and self.plot_settings.units_to:
				try:
					quantity_from = Quantity(1, self.plot_settings.units_from)
					quantity_to = Quantity(1, self.plot_settings.units_to)
				except ValueError as e:
					self.unit_conversion = 0
					MessageDialog(self, str(e), 'Invalid unit').Show()
				else:
					# We don't actually care about the units; just the prefix values.
					self.unit_conversion = math.log(quantity_from.value, 10) - math.log(quantity_to.value, 10)
			else:
				self.unit_conversion = 0

			self.acq_thread.delay = self.plot_settings.delay

			if self.plot_settings.time_value == 0:
				self.plot.x_label = 'Time (s)'
			elif self.plot_settings.time_value == 1:
				self.plot.x_label = 'Points'

			if self.plot_settings.y_scale != 0:
				self.plot.y_label = '/ 10 ^ {0}'.format(self.plot_settings.y_scale)
			else:
				self.plot.y_label = ''

			if self.plot_settings.units_to:
				self.plot.y_label += ' ({0})'.format(self.plot_settings.units_to)

			self.update_plot()

		dlg = PlotSettingsDialog(self, ok_callback)
		dlg.SetValue(self.plot_settings)
		dlg.Show()

	def msg_resource(self, name, value=None):
		if self.measurement_resource_name is not None and name == self.measurement_resource_name:
			self.resource = value

	def msg_data_capture_start(self, name):
		if name == self.measurement_resource_name:
			if self.enabled:
				self.capturing_data = True

				# Keep track of whether to restart the capture afterwards.
				self.restart_live_view = self.running

				# Disable live view.
				self.resource_backup = self.resource
				self.resource = None

	def msg_data_capture_data(self, name, value):
		if name == self.measurement_resource_name:
			if self.capturing_data:
				self.add_value(value)

	def msg_data_capture_stop(self, name):
		if name == self.measurement_resource_name:
			if self.capturing_data:
				self.capturing_data = False

				# Re-enable live view.
				self.resource = self.resource_backup
				self.resource_backup = None

				if self.restart_live_view:
					self.OnRun()
예제 #3
0
class ListLiveViewPanel(wx.Panel):
    """
	A panel to display a live view plot of a list resource.
	"""
    def __init__(self, parent, global_store, *args, **kwargs):
        wx.Panel.__init__(self, parent, *args, **kwargs)

        self.global_store = global_store
        self._measurement_resource_name = None

        # Defaults.
        self.plot_settings = PlotSettings()
        self.unit_conversion = 0

        self.enabled = True
        self.capturing_data = False
        self.restart_live_view = False
        self.resource_backup = None

        # csv save file information
        self.save_path = ''
        self.defaultName = ''

        # Initialize recorded values.
        self.init_values()

        # This lock blocks the acquisition thread from acquiring.
        self.running_lock = Lock()

        # Plot and toolbar.
        display_box = wx.BoxSizer(wx.VERTICAL)

        ## Plot.
        if plot_available:
            self.plot = TwoDimensionalPlot(self, color='blue')
            display_box.Add(self.plot.control, proportion=1, flag=wx.EXPAND)


#			self.plot.x_label = 'Waveform time (s)'
#			self.plot.y_label = 'History'
        else:
            display_box.Add((500, -1), proportion=1, flag=wx.EXPAND)

        ## Controls.
        if plot_available:
            controls_box = wx.BoxSizer(wx.HORIZONTAL)
            display_box.Add(controls_box, flag=wx.CENTER | wx.ALL, border=5)

            ### Manual data export
            saveData_static_box = wx.StaticBox(self, label='Last trace')
            saveData_box = wx.StaticBoxSizer(saveData_static_box,
                                             wx.HORIZONTAL)
            controls_box.Add(saveData_box, flag=wx.CENTER)

            self.csv_button = wx.Button(self, label='Save to .csv')
            self.Bind(wx.EVT_BUTTON, self.onSave, self.csv_button)
            saveData_box.Add(self.csv_button, flag=wx.CENTER)

            ### Capture.
            capture_static_box = wx.StaticBox(self, label='Control')
            capture_box = wx.StaticBoxSizer(capture_static_box)
            controls_box.Add(capture_box, flag=wx.CENTER)

            self.run_button = wx.Button(self, label='Run')
            self.Bind(wx.EVT_BUTTON, self.OnRun, self.run_button)
            capture_box.Add(self.run_button, flag=wx.CENTER)

            self.pause_button = wx.Button(self, label='Pause')
            self.Bind(wx.EVT_BUTTON, self.OnPause, self.pause_button)
            capture_box.Add(self.pause_button, flag=wx.CENTER)

            self.reset_button = wx.Button(self, label='Reset')
            self.Bind(wx.EVT_BUTTON, self.OnReset, self.reset_button)
            capture_box.Add(self.reset_button, flag=wx.CENTER)

            ### Settings.
            settings_static_box = wx.StaticBox(self, label='Settings')
            settings_box = wx.StaticBoxSizer(settings_static_box,
                                             wx.HORIZONTAL)
            controls_box.Add(settings_box, flag=wx.CENTER | wx.LEFT, border=10)

            self.plot_settings_button = wx.Button(self, label='Plot...')
            self.Bind(wx.EVT_BUTTON, self.OnPlotSettings,
                      self.plot_settings_button)
            settings_box.Add(self.plot_settings_button, flag=wx.CENTER)

        self.SetSizer(display_box)

        # Acquisition thread.
        callback = functools.partial(wx.CallAfter, self.add_values)
        self.acq_thread = AcquisitionThread(self.plot_settings.delay,
                                            callback,
                                            running_lock=self.running_lock)
        self.acq_thread.daemon = True
        self.acq_thread.start()

        # Wait for a resource to begin capturing.
        self.OnPause()
        self.run_button.Disable()

        # Subscriptions.
        pub.subscribe(self.msg_resource, 'resource.added')
        pub.subscribe(self.msg_resource, 'resource.removed')
        pub.subscribe(self.msg_data_capture_start, 'data_capture.start')
        pub.subscribe(self.msg_data_capture_data, 'data_capture.data')
        pub.subscribe(self.msg_data_capture_stop, 'data_capture.stop')

    @property
    def running(self):
        return self.pause_button.Enabled

    @property
    def resource(self):
        return self.acq_thread.resource

    @resource.setter
    def resource(self, value):
        # Ignore unreadable resources.
        if value is not None and not value.readable:
            value = None

        if self.running:
            # Currently running.
            running = True
            self.OnPause()
        else:
            running = False

        self.acq_thread.resource = value

        self.run_button.Enable(value is not None)

        # Resume if applicable.
        if running:
            self.OnRun()

    @property
    def measurement_resource_name(self):
        if self._measurement_resource_name is None:
            return ''
        else:
            return self._measurement_resource_name

    @measurement_resource_name.setter
    def measurement_resource_name(self, value):
        if value:
            self._measurement_resource_name = value
            try:
                self.resource = self.global_store.resources[
                    self._measurement_resource_name]
            except KeyError:
                self.resource = None
        else:
            self._measurement_resource_name = None
            self.resource = None

    def init_values(self):
        """
		Clear captured values.
		"""

        self._times = numpy.array([])
        self._values = numpy.array([])

    def update_plot(self):
        """
		Redraw the plot.
		"""

        # Wait for at least one line.
        if not len(self._times) > 0:
            display_time = [0]
            display_values = [0]
        else:
            display_time = self._times
            display_values = self._values

        if self.plot_settings.update_x:
            self.plot.x_autoscale()
        if self.plot_settings.update_y:
            self.plot.y_autoscale()

        self.plot.x_data, self.plot.y_data = display_time, display_values

    def add_values(self, values):
        """
		Update the plot with a new list of values.
		"""

        if not self.plot_settings.enabled:
            return

        # Extract the times and the data values.
        times, values = list(zip(*values))

        # Update values.
        self._values = numpy.append(numpy.array([]), values)
        self._times = numpy.append(numpy.array([]), times)

        # Plot.
        self.update_plot()

    def close(self):
        """
		Perform cleanup.
		"""

        # Unsubscriptions.
        pub.unsubscribe(self.msg_resource, 'resource.added')
        pub.unsubscribe(self.msg_resource, 'resource.removed')
        pub.unsubscribe(self.msg_data_capture_start, 'data_capture.start')
        pub.unsubscribe(self.msg_data_capture_data, 'data_capture.data')
        pub.unsubscribe(self.msg_data_capture_stop, 'data_capture.stop')

        # Ensure the thread exits.
        self.acq_thread.resource = None
        self.acq_thread.done = True
        if not self.running:
            self.running_lock.release()
        self.acq_thread.join()
        del self.acq_thread

    def onSave(self, evt=None):
        """
		save the latest trace to a manually named csv
		"""
        outArray = numpy.column_stack((self._times, self._values))

        self.defaultName = 'listTrace_{0:04}-{1:02}-{2:02}_{3:02}-{4:02}-{5:02}.csv'.format(
            *localtime())
        fdlg = wx.FileDialog(self, "Save last trace", "", self.defaultName,
                             "CSV files(*.csv)|*.*",
                             wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
        if fdlg.ShowModal() == wx.ID_OK:
            self.save_path = fdlg.GetPath()
            if not (self.save_path.endswith(".csv")):
                self.save_path = self.save_path + ".csv"
            numpy.savetxt(self.save_path, outArray, delimiter=',')

    def OnRun(self, evt=None):
        """
		Let the acquisition thread run.
		"""

        self.run_button.Disable()

        if self.acq_thread.resource is None:
            return

        self.running_lock.release()

        self.pause_button.Enable()

    def OnPause(self, evt=None):
        """
		Block the acquisition thread.
		"""

        if not self.running:
            return

        self.running_lock.acquire()

        if self.acq_thread.resource is not None:
            self.run_button.Enable()
        self.pause_button.Disable()

    def OnReset(self, evt=None):
        self.init_values()
        self.update_plot()

    def OnPlotSettings(self, evt=None):
        """
		Open the plot settings dialog.
		"""
        def ok_callback(dlg):
            self.plot_settings = dlg.GetValue()

        dlg = PlotSettingsDialog(self, ok_callback)
        dlg.SetValue(self.plot_settings)
        dlg.Show()

    def msg_resource(self, name, value=None):
        if self.measurement_resource_name is not None and name == self.measurement_resource_name:
            self.resource = value

    def msg_data_capture_start(self, name):
        if name == self.measurement_resource_name:
            if self.enabled:
                self.capturing_data = True

    def msg_data_capture_data(self, name, value):
        if name == self.measurement_resource_name:
            if self.capturing_data:
                self.add_values(value)

    def msg_data_capture_stop(self, name):
        if name == self.measurement_resource_name:
            if self.capturing_data:
                self.capturing_data = False