Example 4: Writing Your Own Plot-Based Dialog Script¶
This example shows how to create your own graphical dialogs with the application wxFormBuilder (wxFB) and to visualize live spectrum plots within the dialog.
The main difference is that Example 4 utilizes matplotlib to visualize the plots, whereas the simple dialog template in Example 3 visualizes/prints the values only.
Note
Narda provides links to offers from third parties in this documentation. The providers of these pages are exclusively responsible for the contents and in particular any damages that may arise from the use or non-use of the information provided in this way. Narda only checks the pages at the time the link is created. All subsequent changes are the responsibility of the provider.
Step 1) Define the ‘Measurement Sequence’ and the ‘Dialog Requirements’¶
This example application is intended to measure real-time spectrum plots and to display them within a graphical dialog. The user should be able to end the plot-based dialog anytime by clicking a button.
To create this application, we can segregate the scripting steps for the measurement sequence and the dialog as follows:
a) Measurement Sequence¶
For the measurements, the scripts must perform the following steps:
Get the measurement settings like detector and setup the corresponding variables
Connect to the device
Add RT Spectrum Task if it does not already exist
Setup the device with the measurement settings e.g. Fstart/Fstop, RBW etc)
Start the spectrum measurements and open the dialog
Acquire spectrum trace values and send them to the dialog using the “update_progress” method
Update the spectrum plots in real-time within the dialog until the “stopevent” flag is set
Disconnect from the device
b) Dialog Requirements¶
The dialogs must perform the following tasks to show the live spectrum measurements:
Plot the real-time spectrum traces in a panel inside the dialog
Show a progress bar indicating that the measurements are running
Show a static status label and an icon
Show a “Cancel” button to end the dialog
This application requires the measurement sequence in step 1a) to interact with the dialogs defined in step 1b) from the start of the measurements till the end of the measurements. Therefore, the scripts are created using the multithreading template tmpusrscriptmthread.py that is described in Example 2. The next steps of this example will explain how to modify the multithreading template to add a dialog and meet the requirements stated in step 1a) and 1b).
The “MeasDlgThread” is the base class for the measurement thread that keeps the graphical user interface (GUI) alive while the measurements are being performed. This base class offers a standard dialog to display control buttons, a status label, a progress bar and an icon. It also supports the use of a user-defined dialog based on the class “DataVisualizationDlg” for additional visualization of measurement results. It is important to note the following:
The dialog classes can be accessed within the “_run_script” of the tmpusrscriptmthread.py only.
It is not possible to access the controls of the dialog directly from within the measurement method “_run_measurement” of the tmpusrscriptmthread.py. To access these controls, please use the method “update_progress” to update the buttons, the status label, the icon or the progress bar and to send measurement data to the dialog.
More details on the MeasDlgThread and DataVisualizationDlg can be found in chapter Additional Multithreading Functions: in the Scripting Tutorial.
Step 2) Using a wxFB Plot-Based Dialog Template¶
The wxFormBuilder (wxFB) is an open source tool for creating graphical dialogs. It can be downloaded from a third party link here:
https://sourceforge.net/projects/wxformbuilder/
This step will help a user to create a dialog in the wxFB using a project template PlotDialogs.fbp and will also help to generate scripts that can interact with the data visualization dialog class (DataVisualizationDlg).
a) Start with the Base Dialog Template¶
Predefined wxFB project template PlotDialogs.fbp is available for dialog creation. It provides two base dialogs that can be modified by the user as per the requirements:
PlotSettingsBaseDlg (wxDialog) is a simple dialog
PlotBaseDlg (DataVisualizationDlg) can be used together with the class “MeasDlgThread” to measure values in a separate thread and display them in the main GUI thread

The ‘dialog creation sequence’ in step 1b) requires data and progress update from the main thread, therefore, only PlotBaseDlg will be used in this example. The “MeasDlgThread” class will measure the values of the spectrum trace in a separate thread and will allow the dialog to access them for display in the main GUI thread. The PlotBaseDlg contains the following widgets that are also supported by the base class “DataVisualizationDlg”:
wxPanel: A panel to display the live plots
wxGauge: To display the progress bar
wxStaticText: To show the status of plotting measurements
wxStaticBitmap: To display icons
wxStdDialogButtonSizer: A standard button to stop measurements and close the dialog
For more complex dialogs, it is helpful to make a rough sketch of the dialog structure first, and add/delete the required widgets by modifying the provided template PlotDialogs.fbp.
b) Adapt the Base Dialog Template¶
Adapt the template file according to the defined goal in step 1b). The PlotBaseDlg from the template file PlotDialogs.fbp used for this example gives the following adapted dialog:

As the measurement template cannot access the GUI template directly, we use the data visualization class (DataVisualizationDlg) from measurement threading abstraction layer to update the progress bar within the dialog. For this purpose, DataVisualizationDlg must be defined as the subclass of the PlotBaseDlg in the properties tab of the dialog:

Note
For other applications, more wx.widgets can be added/removed within the project template PlotDialogs.fbp as per the requirement. A number of widgets can be found in the component palette of wxFB:

For more details on the wxFB, the following external link can be used:
https://www.tutorialspoint.com/wxpython/wxpython_quick_guide.htm
c) Generate Script for the Base Dialog¶
The project template PlotDialogs.fbp provides a graphical layout of the dialogs only. Once the layout is created as per the user requirement, the dialog script can be created using the “Generate Code” button on the main palette of the wxFB application as shown in the figure below:

Before generating the code, the path of the wxFB project must be set to the project directory of the IDE containing the measurement scripts.
The object properties of the project template PlotDialogs.fbp shows several code generation options provided by wxFB.

We use Python as the code generation language. Once the generate button is pressed by the user, a script plotdialogsbase.py is created with a .py extension. Hence, the template PlotDialogs.fbp provides only the graphical layout of the dialogs in a project file, whereas the template plotdialogsbase.py provides the python script generated for this wxFB project.
The naming conventions also need to be taken into account before generating the code:
The project file name becomes the module name with .py extension
The dialog name becomes the class name
The object names become the class variables
The event names for each object become the class methods
Note
It is recommended to use an IDE such as PyCharm to view and edit a script. These offer the possibility of debugging the script execution in addition to auto-completion of the code.
The Narda Script Launcher API uses four spaces per indentation. The type of indentation must not be changed within a script!
The script plotdialogsbase.py generated with PlotDialogs.fbp template imports all the relevant modules itself and declares two classes i.e. PlotSettingsBaseDlg for a simple dialog, and PlotBaseDlg for multithreading dialog derived from DataVisualizationDlg class.
Do not edit the base dialog file plotdialogsbase.py generated with wxFB.
Step 3) Using the Dialog Event Handling Template¶
a) Start with the Dialog Events Template¶
As mentioned in the previous step, we do not edit the base dialog script plotdialogsbase.py generated by the wxFB.
For event handling, we use the template plotdialogs.py that is derived from the base dialog template script plotdialogsbase.py. This event handling template file allows to override any virtual event handler used in the base script and to define new classes to cater for the needs of the application.
b) Adapt the Dialog Events Template¶
The derived classes from the base classes generated in Step 2) are:
PlotSettingsDlg (PlotSettingsBaseDlg): This class represents a simple wxDialog and overrides the events of the base class PlotSettingsBaseDlg.
SpectrumPlotDlg(PlotBaseDlg): This class is derived from the base class dialog created for the visualization of data. As DataVisualizationDlg is its parent class, it allows the use of the update_progress method where the live measurement data for the selected trace is taken from the signal shark and is used for plotting the graphs within the dialog. The event handlers of PlotBaseDlg are overridden by the SpectrumPlotDlg class.
For this application, we code the SpectrumPlotDlg class only. The PlotSettingsDlg remains unused for this example.
It is important to note that the base dialog (PlotBaseDlg) derived from the data visualization class (DataVisualizationDlg) using wxFB does not override the update_progress function. Therefore, the event handling class (SpectrumPlotDlg) has to override this function.
A detailed description of the “update_progress” function can be found in chapter Additional Multithreading Functions: in the Scripting Tutorial.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | class SpectrumPlotDlg(PlotBaseDlg):
"""Custom dialog class for data visualization."""
def __init__(self, parent, title: str, spectrum_settings: SpectrumSettings):
"""Dialog class for data visualization.
:param parent: The parent window as wx.Window.
:param title: The dialog caption.
:param spectrum_settings: The spectrum settings.
"""
super().__init__(parent=parent)
self.init_progress_bar(self.pgb_progress)
self.init_dlg_buttons(self.sdbs_buttons, wx.OK)
self.btn_ok.SetLabel('Quit')
self.SetTitle(title)
self.spectrum_settings = spectrum_settings
# self.Maximize(True)
# Add plot area:
# ---------------------------------------------------------------------
self.plt = TracePlotCanvasPnl(self.pnl_plot_container)
self.plt.update_settings(spectrum_settings)
self.szr_plot.Add(self.plt, 1, wx.EXPAND | wx.ALL, 5)
def update_progress(self, evt: ProgressEvent):
"""Receives data from thread and updates progressbar and message text
:param evt: Event of type ProgressEvent.
"""
# Handle progress
self.update_progress_bar(evt, self.pgb_progress)
# Handle message
if evt.msg is not None:
self.lbl_status.SetLabelText(evt.msg)
# Handle data:
if evt.data is not None:
for trace_data in evt.data:
self.plt.update_y_values(trace_data[0], trace_data[1])
self.plt.update_plot()
# Handle buttons:
if evt.btn_style:
self.update_btn_style(evt.btn_style)
# Handle icons:
if evt.icon_style:
self.update_icon_style(self.bmp_icon, evt.icon_style)
# Handle help text:
if evt.help_text:
self.help_text = evt.help_text
self.Layout()
self.Refresh()
self.Update()
# Code here --------------------------------------------------------------------------------------
# Add more methods here if required
# If virtual event handlers are used in MyMeasGUIBaseDlg, over-ride them here in the derived class
# ------------------------------------------------------------------------------------------------
|
However, two more classes are defined to provide added functionality to this application:
SpectrumSettings: This is a Python data class that is used to fetch the data from the signal shark, perform calculations on it, and to pass this data to the dialog e.g. frequency calculations, amplitude calculations etc. The data from this data class can then be referenced for drawing the plots within the dialog.
TracePlotCanvasPnl(wx.Panel): This is the class that handles the plots in the vacant wxPanel of the dialog shown in Step 2b, and is therefore derived from the wx.Panel module of the wxPython. It uses the matplotlib library of Python to allow different graphical plotting functions for displaying the spectrum trace and the limit lines within the wxPanel.
More details on the use of matplotlib with wxPython can be found here: Integrating matplotlib with wxPython Dialogs.
To access the wxPanel of the SpectrumPlotDlg and to add plots to it for each trace of the data provided by the SpectrumSettings class via the “update_progress” function, the class TracePlotCanvasPnl is used.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | @dataclass
class SpectrumSettings:
"""Class for keeping track of measurement parameters."""
# General settings
valid: bool = False
meas_time:float = 0.0
comment: str = ''
# Amplitude settings
trace_list: [] = None
trace_3rd_detector: Spec3rdDetectorModes = Spec3rdDetectorModes.UNKNOWN
trace_infinite: bool = False
input: int = 1
atten: float = 0.0
unit: LevelUnits = LevelUnits.UNKNOWN
lmax: float = 0.0
lrange: float = 0.0
# Frequency settings
fstart: float = 0.0
fstep: float = 0.0
bin_count: int = 0
rbw:float = 0.0
rbw_filter_type:RbwFilterTypes = RbwFilterTypes.UNKNOWN
@property
def fstop(self):
"""Calculates the stop frequency"""
return self.fstart + (self.fstep*self.bin_count)
def get_flist(self, divider:float = 1.0):
""" Calculates the x-axis frequencies
:param divider: Factor by which the frequency is divided (e.g. 1.0e6 to represent MHz)
:type divider: float
:return:
"""
retvalue = []
if self.bin_count > 0 and divider != 0.0:
freq = self.fstart / divider
freq_step = self.fstep / divider
retvalue.append(freq)
for x in range(self.bin_count-1):
freq += freq_step
retvalue.append(freq)
return retvalue
@property
def lmin(self):
"""Calculates the y-axis minimum level"""
return self.lmax - self.lrange
def clone(self):
"""Clones the current data class"""
retvalue = None
try:
retvalue = SpectrumSettings()
retvalue.trace_list = self.trace_list
retvalue.trace_3rd_detector = self.trace_3rd_detector
retvalue.trace_infinite = self.trace_infinite
retvalue.input = self.input
retvalue.atten = self.atten
retvalue.unit = self.unit
retvalue.lmax = self.lmax
retvalue.lrange = self.lrange
retvalue.fstart = self.fstart
retvalue.fstep = self.fstep
retvalue.bin_count = self.bin_count
retvalue.rbw = self.rbw
retvalue.rbw_filter_type = self.rbw_filter_type
retvalue.meas_time = self.meas_time
retvalue.comment = self.comment
except:
retvalue = None
return retvalue
@staticmethod
def from_device(dev: SignalSharkDev):
""" Creates a SpectrumSettings object and reads data from device
:param dev: The SignalShark device
:type dev: SignalSharkDev
:return: SpectrumSettings
:rtype: SpectrumSettings
"""
retvalue = None
try:
retvalue = SpectrumSettings()
if dev.connect():
dev.scpi.check_error()
retvalue.trace_list = dev.scpi.spectrum.get_trace_list()
retvalue.trace_3rd_detector = dev.scpi.spectrum.get_trace_3rd_detector()
retvalue.trace_infinite = dev.scpi.spectrum.get_trace_infinite()
retvalue.input = dev.scpi.sense.get_input()
retvalue.atten = dev.scpi.sense.get_attenuator()
retvalue.unit = dev.scpi.display.get_unit()
retvalue.lmax = dev.scpi.display.get_spectrum_lmax()
retvalue.lrange = dev.scpi.display.get_spectrum_lrange()
retvalue.fstart = dev.scpi.spectrum.get_data_frequency_start()
retvalue.fstep = dev.scpi.spectrum.get_data_frequency_step()
retvalue.bin_count = dev.scpi.spectrum.get_data_count()
retvalue.rbw = dev.scpi.spectrum.get_rbw()
retvalue.rbw_filter_type = dev.scpi.spectrum.get_rbw_filter_type()
retvalue.meas_time = dev.scpi.spectrum.get_measurement_time()
retvalue.valid = dev.scpi.check_no_error()
except:
retvalue = None
return retvalue
class TracePlotCanvasPnl(wx.Panel):
"""wxWidget plot panel based on matplotlib plot"""
def __init__(self, parent, tag: str = ''):
""" wxWidget plot panel based on matplotlib plot
:param parent: The parent widget.
:type parent: wx.Widget
:param tag: Optional string tag.
:type tag: str
"""
super(TracePlotCanvasPnl, self).__init__(parent, size=(450, 360))
self.tag = tag
self.figure = Figure()
self.axes = self.figure.add_subplot(111)
self.canvas = FigureCanvas(self, -1, self.figure)
# Workaround for size issue
self.canvas.SetMinSize(wx.Size(10, 10))
# self.toolbar = NavigationToolbar(self.canvas)
# self.toolbar.Realize()
self.x_values = []
self.lines = {}
self.ty = []
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, proportion=1, flag=wx.ALL | wx.GROW)
# self.sizer.Add(self.toolbar, 0, wx.LEFT | wx.EXPAND)
self.SetSizer(self.sizer)
self.Fit()
self.trace_colors = {
'': '#000000',
'UNKNOWN': '#000000',
'RMS': '#00C000',
'PPk': '#FF0000',
'MPk': '#0000FF',
'Avg': '#0000FF',
'Smp': '#0000FF',
'MnR': '#003955',
'AvR': '#FF00FF',
'MxR': '#553900',
'MxP': '#AA0000',
'MnP': '#0000AA',
'MxA': '#0000AA',
'MxS': '#0000AA'
}
def clear_memory(self):
"""Clears the actual traces"""
self.lines = {}
for i, line in enumerate(self.axes.lines):
self.axes.lines.pop(i)
line.remove()
self.x_values = []
def update_settings(self, spectrum_settings: SpectrumSettings, title:str = ''):
"""
:param spectrum_settings: Measurement settings of the Spectrum View.
:type spectrum_settings: SpectrumSettings
:param title: Title of the plot panel.
:type title: str
"""
self.clear_memory()
self.axes.set_ylabel('Power/{}'.format(spectrum_settings.unit))
self.axes.set_xlabel('Frequency/MHz')
self.axes.set_title(title)
self.axes.set_autoscale_on(False)
self.axes.set_autoscalex_on(False)
self.axes.set_autoscaley_on(False)
self.axes.grid(True)
self.axes.set_xbound(spectrum_settings.fstart / 1e6, spectrum_settings.fstop / 1e6)
self.axes.set_ybound(spectrum_settings.lmin, spectrum_settings.lmax)
self.figure.subplots_adjust(left=0.2, right=0.9, bottom=0.2, top=0.9)
self.x_values = spectrum_settings.get_flist(1e6)
# self.ty = [float(self.user_selected_value)] * spectrum_settings.bin_count
# self.axes.plot(self.x_values, self.ty)
for trace in spectrum_settings.trace_list:
trace_name = str(trace)
y_values = [0.0]*spectrum_settings.bin_count
self.lines[str(trace)] = self.axes.plot(self.x_values, y_values,
color=self.trace_colors.get(trace_name, '#000000'),
label=trace_name)
self.axes.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc='lower left',
ncol=4, mode='expand', borderaxespad=0.)
def update_y_values(self, trace: str, y_values: list):
""" Updates a trace with new y data.
:param trace: Trace name
:type trace: str
:param y_values: List of level values.
:type y_values: list of float
"""
line = self.lines.get(trace)
if not line:
return
line[0].set_ydata(y_values)
def update_plot(self):
self.canvas.draw()
|
Note
For other applications, if virtual event handlers are used in any of the base dialog classes (PlotSettingsBaseDlg or PlotBaseDlg), over-ride them here in the derived class.
c) Add Packages to the Import Section of Module¶
In this example we derive the event class from the base class generated with wxFormBuilder. To import the base class in the event class, add the following line of code to the import section:
from .plotdialogsbase import PlotSettingsBaseDlg, PlotBaseDlg
Additional packages that are imported to allow for the use of wxPython functions and the matplotlib functions are as follows:
import wx
import matplotlib as mpl
mpl.use(‘WXAgg’)
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
To use the python dataclass, the following line of code is used:
from dataclasses import dataclass
1 2 3 4 5 6 7 8 9 10 11 12 13 | import wx
import matplotlib as mpl
mpl.use('WXAgg') # switch backend (case insensitive)
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wxagg import NavigationToolbar2WxAgg as NavigationToolbar
from operator import itemgetter, attrgetter
import numpy as np
from dataclasses import dataclass
from nardascripting.base.signalsharkdev import *
from nardascripting.base.usrscriptbase import *
from .plotdialogsbase import PlotSettingsBaseDlg, PlotBaseDlg
from nardascripting.base.measthread import *
|
Step 4) Using the Measurement Template¶
Predefined template files are available for various kind of applications.
For more information see: Script Templates
a) Start with the Multithreading Template¶
The ‘measurement sequence’ as described in step 1a) takes an indefinite time. Therefore we have to use to use the template tmpusrscriptmthread.py. The main difference with the simple template is that it utilizes multithreading to keep the graphical user interface “alive”.
b) Adapt the Multithreading Template¶
In this step we have to adapt the template file tmpusrscriptmthread.py according to our defined goal. The adapted script for this example is provided as the template rxspectrum.py.
Note
It is recommended to use an IDE such as PyCharm to view and edit a script. These offer the possibility of debugging the script execution in addition to autocompletion of code.
The Narda Script Launcher API uses four spaces per indentation. The type of indentation must not be changed within a script!
Name the Script and Add a Description¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class SpectrumMeasurement(UsrScriptBase):
"""User script class"""
def __init__(self, main_gui, dev=SignalSharkDev()):
"""Initialization. Please leave this line unchanged"""
super().__init__(main_gui, dev, __file__)
# Script settings.
# -------------------------------------------------------------------------------
# Please adapt the following lines of code according to your script.
self._tab_name = 'Examples'
self._scr_title = 'Examples04 - Spectrum Plots'
self._scr_description = 'Acquires spectrum data and shows them in a plot dialog.'
# self._icon_path = self.script_path.joinpath('NardaScriptLauncher_example01.png')
self._list_prio = 4
self._nsl_executed_behavior = NSL_Executed_Behaviors.SHOW_NSL
# Add class variables if needed
# -------------------------------------------------------------------------------
# self.my_variable = None
|
Code the Defined Measurement Sequence and Dialog In “_run_script(”¶
This part of the code deals with both scripting elements described in step 1a) and 1b), i.e. the measurement sequence and the dialog. Hence, the dialog and measurement thread, both need to be instantiated here.
To instantiate the dialog for data visualization, add the following line of code in the “_run_script” method:
meas_dlg = SpectrumPlotDlg(self.main_gui, ‘Spectrum Plot’, self.spectrum_settings)
This meas_dlg is then passed in the instantiation of the measurement thread base class as an optional parameter dlg, whereas the measurements are fetched with the callback parameter from the “_run_measurement” method:
- mthread = MeasDlgThread(main_gui=self.main_gui, dlg = meas_dlg,
callback = MyMeasurement._run_measurement, args=[self.signalshark.addr])
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | def _run_script(self, args):
""" Script main function
This method is called when a user clicks on the corresponding script button.
Use the dialog classes only in this part of the code.
"""
# Connect to the device
# -------------------------------------------------------------------------------
if not self.signalshark.connect():
self.MessageBoxModal('Cannot connect to the device!', 'Connection error',
wx.OK | wx.ICON_ERROR)
return
# Clear SCPI error queue
self.signalshark.scpi.check_error()
# Setup Spectrum View:
# -------------------------------------------------------------------------------
# Add a Spectrum View if it does not already exist and configure it
exists, view_index = self.signalshark.scpi.check_add_view(ViewTypes.SPECTRUM)
if view_index < 1:
self.MessageBoxModal('Adding Spectrum View not possible.',
'Level Meter View', wx.OK | wx.ICON_ERROR)
self.signalshark.disconnect()
return
if not exists:
self.MessageBoxModal('Please configure the newly added Spectrum View (e.g. Fstart/Fstop, RBW, ...)',
'Spectrum View', wx.OK | wx.ICON_INFORMATION)
self.signalshark.disconnect()
return
# Read spectrum settings from device:
self.spectrum_settings = SpectrumSettings.from_device(self.signalshark)
if not self.spectrum_settings:
self.MessageBoxModal('Error reading spectrum settings', 'Settings Error',
wx.OK | wx.ICON_ERROR)
self.signalshark.disconnect()
return
self.signalshark.disconnect()
# Instantiate dialog event class.
# -------------------------------------------------------------------------------
# Pop up dialog for level value
# Dialog for spectrum plots
meas_dlg = SpectrumPlotDlg(self.main_gui, 'Spectrum Plot', self.spectrum_settings)
# Create an additional measurement thread to keep the GUI alive.
# Please change 'MyUserScript._run_measurement' according to your class name.
# With "args=[param1, parm2, paramn]" you can pass some optional parameters to the measurement thread.
mthread = MeasDlgThread(main_gui=self.main_gui, dlg=meas_dlg,
callback=SpectrumMeasurement._run_measurement,
args=[self.signalshark.addr])
# Start the measurement thread and show a visualization (progress) dialog.
mthread.start_measurement()
# The lines of code below will be executed after the measurement thread has done it's job.
# ...
|
Code the Thread Function “_run_measurement(”¶
This part of the code deals with the measurement thread only. The ‘measurement sequence’ defined in step 1b) is coded here using the multithreading template.
The trace data is taken from the SignalShark and the update progress bar is used to send the data to the dialog for visualization. The thread stays alive as long as a stopevent is not set.
A detailed description of the functions “stopevent”, “update_progress” and “wait_for_stopevent” can be found in chapter Additional Multithreading Functions:.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | @staticmethod
def _run_measurement(stopevent, update_progress, wait_for_stopevent,
signalshark_addr: str):
""" Main method of the measurement thread that handles a time consuming measurement.
Please do NOT use any wxPython elements like a MessageBox or a Dialog here!
They are only permitted in the GUI thread ('_run_script' method).
Please use the "update_progress" method to display information!
:param stopevent: Thread flag that indicates whether the procedure should be finished/cancelled
:param update_progress: Delegate method to update a progress bar, message string and icon
:param wait_for_stopevent: Delegate method to wait until stopevent is raised with possibility to show a message.
:param signalshark_addr: Custom parameter to handover the ip address of the SignalShark
:return: Optional custom parameter
"""
# Setup connection to the SignalShark and display error message if connection fails.
signalshark = SignalSharkDev(signalshark_addr)
if not signalshark.connect():
return wait_for_stopevent('Cannot connect to the device!', icon_style=wx.ICON_ERROR)
try:
# Update the dialog message text and show a progress bar
update_progress(msg='Measurement running', show_progress=True)
trace_list = signalshark.scpi.spectrum.get_trace_list()
# Loop, that simulates a time consuming measurement
last_scan_number = -1
while True:
if stopevent.is_set():
return
# Example measurement:
# Wait until Scan Number has increased.
scan_number = signalshark.scpi.spectrum.get_data_update(last_scan_number)
if scan_number < 0:
return wait_for_stopevent('Cannot acquire measurement data!', icon_style=wx.ICON_ERROR)
elif scan_number > last_scan_number:
last_scan_number = scan_number
else:
if signalshark.scpi.check_error():
return wait_for_stopevent(str(signalshark.scpi.error_str))
# Wait for new data
stopevent.wait(0.5)
continue
trace_data_list = []
for trace in trace_list:
trace_data_list.append([str(trace), signalshark.scpi.spectrum.get_data_level(trace)])
# Send new values to GUI.
update_progress(data=trace_data_list)
# Allow the thread to synchronize the stop event.
stopevent.wait(0.1)
except Exception as e:
# Do some error handling
return wait_for_stopevent(str(e), icon_style=wx.ICON_ERROR)
finally:
# Close connection.
signalshark.disconnect()
|
c) Add Packages to the Import Section of Module¶
If any of the used packages in the measurement script is not a part of the standard Python library, then import it before use.
In this example the dialog event class and the data class are instantiated for use in measurement thread. To import these classes, add the following line of code to the import section:
from .gui.plotdialogs import SpectrumPlotDlg, SpectrumSettings
1 2 3 4 5 6 | from pathlib import Path
import wx
from nardascripting.base.usrscriptbase import *
from nardascripting.base.signalsharkdev import *
from nardascripting.base.measthread import MeasDlgThread
from .gui.plotdialogs import SpectrumPlotDlg, SpectrumSettings
|
Step 5) Debug the Script¶
It is recommended to use an IDE such as PyCharm to create and edit a script. These offer the possibility of debugging the script execution in addition to autocompletion of code.
The application note “Developing Scripts for SignalShark with PyCharm.pdf”, which can be downloaded from the Narda website, describes how to use the Narda Script Launcher together with PyCharm.
Note
The Narda Script Launcher API uses four spaces per indentation. The type of indentation must not be changed within a script!
You can view and download the complete script example here example4.zip