QView User InterfaceQWin™ GUI Prototyping Toolkit
QView™ has been specifically designed for extensibility, so that you can customize its behavior to your specific project. In fact, as mentioned in the section about running QView, the qview.py script takes the <cust-script>
command-line parameter, which is another Python script designed specifically to extend and customize the basic functionality. This section describes how to write this customization script, so that you can turn QView™ into a powerful remote Human-Machine Interface (HMI) for your embedded system.
Custom Menu
The simple way of customizing QView™ is to add custom menus.
Custom Canvas
The main part of the QView™ interface that you customize for your specific embedded system is the custom Canvas. The custom Canvas can show a complete custom Human-Machine Interface (HMI) to your embedded Target. The Canvas can display the changing status of the application and also it can provide actuators, like buttons, sliders, etc. to interact with the embedded Target. The following animation shows the custom Canvas for the DPP example (dpp.py script explained later). Please note that the button in the center of the screen allows you to interact with the Target by posting events to it. Please refer to this script code for the details of how various effects have been achieved.
Custom Canvas for the DPP Application
- Note
- QView™ GUI is based on Tkinter and therefore it is highly recommended that you get familiar with this Python library. Specifically of interest for customizing QView™ is the Tkinter Canvas.
Many Python widget libraries (e.g., tk_tools, etc.) are available to aid you in developing your custom canvas. With such widget libraries, you can display the data in an attractive form: as gauges, bars, graphs, and sliders. The attractiveness of the GUI is limited only by your creativity.
Customization Script
The easiest way to customize QView is to copy the provided example script (dpp.py, located in <qpc|qpcpp>/examples/workstation/dpp/qview folder), rename it, and add your own extensions. The explanation section following the listing clarifies the interesting lines of code (lines starting with [xx]
labels).
[1] class DPP:
[2] def __init__(self):
[3] reset_target()
[4] QView.custom_menu.add_command(label="Custom command",
command=self.cust_command)
[5] QView.show_canvas()
[6] QView.canvas.configure(width=400, height=260)
[7] self._act_img = (
[8] PhotoImage(file=HOME_DIR + "/inline/thinking.gif"),
PhotoImage(file=HOME_DIR + "/inline/hungry.gif"),
PhotoImage(file=HOME_DIR + "/inline/eating.gif"),
)
self._philo_img = (\
QView.canvas.create_image(190, 57, image=self._act_img[0]),
QView.canvas.create_image(273, 100, image=self._act_img[0]),
QView.canvas.create_image(237, 185, image=self._act_img[0]),
QView.canvas.create_image(146, 185, image=self._act_img[0]),
QView.canvas.create_image(107, 100, image=self._act_img[0])
)
self.img_UP = PhotoImage(file=HOME_DIR + "/inline/BTN_UP.gif")
self.img_DWN = PhotoImage(file=HOME_DIR + "/inline/BTN_DWN.gif")
self.btn = QView.canvas.create_image(200, 120, image=self.img_UP)
[9] QView.canvas.tag_bind(self.btn, "<ButtonPress-1>", self.cust_pause)
[10] def on_reset(self):
[11] self._philo_obj = [0, 0, 0, 0, 0]
self._philo_state = [0, 0, 0]
[20] def on_run(self):
[21] glb_filter("QS_USER_00")
[22] current_obj(OBJ_AO, "Table_inst")
[23] loc_filter(IDS_AO)
[24] self._philo_obj = tuple(self._philo_obj)
self._philo_state = tuple(self._philo_state)
[30] def cust_command(self):
[31] command(1, 12345)
[40] def cust_pause(self, event):
[41] if QView.canvas.itemcget(self.btn, "image") != str(self.img_UP):
[42] QView.canvas.itemconfig(self.btn, image=self.img_UP)
[43] post("SERVE_SIG")
[44] QView.print_text("Table SERVING")
else:
QView.canvas.itemconfig(self.btn, image=self.img_DWN)
post("PAUSE_SIG")
QView.print_text("Table PAUSED")
[50] def QS_USER_00(self, packet):
[51] data = qunpack("xxTxBxZ", packet)
i = data[1]
j = ("t", "h", "e").index(data[2][0])
QView.canvas.itemconfig(self._philo_img[i], image=self._act_img[j])
QView.print_text("%010d Philo %1d is %s"%(data[0], i, data[2]))
[60] QView.customize(DPP())
Initialization
- 1
- Every QView™ customization consists of a Python class which does not extend any other class and can be called with any name you see fit.
- 2
- The constructor of your customization class sets up the GUI and optionally resets the target.
- 3
- The reset_target() command allows you to remotely reset the embedded Target.
NOTE: Normally for an embedded application, you would like to start with resetting the Target, to start clean with QS dictionaries, etc. However, if you run your application on the host (e.g., <qpc|qpcpp>/examples/workstation/dpp
), you typically don't want to reset the target. Instead, you simply launch the host executable after opening QView™, so that it will "see" all the QS dictionaries, etc. This alternative is shown in the DPP example for the host located in <qpc|qpcpp>/examples/workstation/dpp
).
- 4
- The QView.custom_menu object allows you to extend the Custom Menu with your own commands. Here, you just add the command "Custom command". Later you will write the function that handles this menu item. NOTE: The QView.custom_menu object uses the Tkinter Menu interface.
Setting Up the Canvas
The Custom Canvas is set up in the constructor of the customization class __init__(self)
) using the QView.canvas object:
- 5
- The QView.show_canvas() function lets you show the canvas from the beginning. This corresponds to setting the Canvas checkbox in the View Menu.
- 6
- The QView.canvas object can be configured to the desired size using the standard Tkinter functionality.
NOTE: The QView.canvas object uses the Tkinter Canvas interface.
- 7-8
- You need to organize the GUI elements for the canvas, so that they easily map to your specific customization. Here, the images representing the Dining Philosophers activities and Philosophers themselves sitting around the table are represented as Python tuples. NOTE: The proper design of the data structures for your application can vastly simplify the customization script. Please remember to take advantage of the rich Python data structures, such as lists, tuples, dictionaries and arrays.
- 9
- This line demonstrates how to provide a keyboard binding to a canvas element (the center button
self.btn
in this case).
The on_reset() Callback
The main qview.py script calls the on_reset() callback function in your customization script when it receives the reset packet from the Target. This gives you an opportunity to clear your internal data structures to get them ready for new data coming from the Target (e.g., QS Dictionaries).
- 10
- The on_reset() callback must be a member of the customization class (note the
self
parameter).
- 11
- Here, the callback clears the internal lists used by customization.
The on_run() Callback
The main qview.py script calls the on_run() callback function in your customization script when it receives the #QS_QF_RUN trace record from the Target. This record typically marks the end of the Target initialization and, specifically, the completion of the QS Dictionaries. This gives you an opportunity to set the QS Filters, Current Objects, using their symbolic names.
- Note
- The symbolic names inside the Target are known to QSPY only after the Target produces the QS Dictionaries.
- 20
- The on_run() callback must be a member of the customization class (note the
self
parameter).
- 21
- Here, the callback sets the Global Filter by means of the glb_filter() facility provided in the qview.py script.
- 22
- Here, the callback sets the Current Objects by means of the current_obj() facility provided in the qview.py script.
- 23
- Here, the callback sets the Local Filter by means of the loc_filter() facility provided in the qview.py script.
- Note
- The glb_filter(), current_obj(), and loc_filter() facilities in QView™ are identical to those in the qview. This commonality between QView™ and qview™ is intentional.
Custom Commands
The Custom Commands, added either to the QView.custom_menu or attached to the Canvas elements, need to be implemented as member functions of the customization class:
- 30
- The function name must match the
command=self.cust_command
parameter in the menu.add_command()
(see step [4] of the script).
- 31
- Here, the function invokes the command() facility provided in the qview.py script.
-
- 40
- The function name must match the
...tag_bind(... self.cust_pause)
parameter (see step [9] of the script).
- 41
- Here, the function checks the Canvas elements (using the standard Tkinter functionality).
- 42
- Here, the function configures the Canvas elements (using the standard Tkinter functionality).
- 43
- Here, the function posts an event to the Target by means of the post() facility provided in the qview.py script.
- 43
- Here, the function prints text to the Text View by means of the QView.print_text() facility provided in the qview.py script.
- Note
- The command() and post() facilities in QView™ are identical to those in the qview. This commonality between QView™ and qview™ is intentional.
Custom Packet Handlers
The most important feature of QView™ is that it can display the live status of the embedded system. QView™ achieves it by receiving the QS software tracing packets from the Target. Consequently, one of the most important activities of the customization script is to intercept and parse the incoming packets.
To this end, the qview.py script provides special accumulations:
- the qview.py script provides a packet-specific callback for every QS trace record from the Target. The name of the callback is identical to the enumerated names of the #QSpyRecords of the trace records. For example, the packet-handler for the QS trace record #QS_QEP_TRAN is named
QS_QEP_TRAN()
.
- the qview.py script provides the qunpack() facility, specifically designed to parse and unpack the QS trace records.
- 50
- The packet-handler name must match the enumeration #QSpyRecords. Here, the packet-handler is for the #QS_USER_00 trace record.
- 51
- Here, the packet-handler calls the qunpack() facility to "unpack" the binary data from the received packet into the Python tuple
data
. Each element in this tuple corresponds to the explicitly specified format. For example, with the format "xxTxBxZ": the "T" format (timestamp) is unpacked into data[0]
, the "B" format (byte) is unpacked into data[1]
, and the "Z" format (zero-terminated ASCII string) is unpacked into data[2]
. Subsequently, in the body of the function, the data[]
elements are used according to their meaning.
- Note
- The qunpack() facility "understands" all formats supported by the Python
struct.unpack()
plus formats specific to QP/Spy, such as: "T" for timestamp, "S" for event signal, ""
Registering Customization in QView™
- 60
- Every QView™ customization script must end with a call to the QView.customize() function, to which you need to pass the instance of your Customization class (here, your
DPP
class).
App-Specific vs. Predefined Trace Records
The customization script discussed in the previous section was based on the application-specific trace record (QS_USER_00
), which was specifically produced by the DPP application. This trace record delivered the information about the changing status of the Dining Philosophers, which allowed the customization script to animate the Philosopher images on the custom canvas.
However, it is also possible to use a different strategy based on the predefined QS trace records. The advantage is that no additional instrumentation is needed in the application, because the information is already produced by the framework. All you need to do is to enable (in the global filter) the appropriate QS trace record.
This alternative strategy is illustrated in the dpp1.py script (located in the same directory as the dpp.py script discussed earlier). The dpp1.py script is based on the predefined QS record #QS_QEP_TRAN, which provides the information about the state changes in the Philo objects in the application.
The most tricky part in this approach is to correctly unpack the predefined record. And here, the biggest challenge is to translate the binary addresses of the objects to their symbolic names. For that, the dpp1.py script intercepts the QS dictionary records #QS_OBJ_DICT and #QS_FUN_DICT, from which the script retrieves the binary addresses of the specific objects. Below are the relevant snippets from the dpp.py script. Please note the use of Python lists, tuples, and dictionaries to organize the data for easy access by the index number of the Philo objects or by the state name.
class DPP:
. . .
def QS_OBJ_DICT(self, packet):
data = qunpack("xxOZ", packet)
try:
i = ("Philo_inst[0]",
"Philo_inst[1]",
"Philo_inst[2]",
"Philo_inst[3]",
"Philo_inst[4]").index(data[1])
self._philo_obj[i] = data[0]
except:
pass
def QS_FUN_DICT(self, packet):
data = qunpack("xxFZ", packet)
try:
j = ("Philo_thinking",
"Philo_hungry",
"Philo_eating").index(data[1])
self._philo_state[j] = data[0]
except:
pass
def QS_QEP_TRAN(self, packet):
data = qunpack("xxTSOFF", packet)
try:
i = self._philo_obj.index(data[2])
j = self._philo_state.index(data[4])
QView.canvas.itemconfig(self._philo_img[i],
image=self._act_img[j])
QView.print_text("%010d Philo %d is %s"\
%(data[0], i, ("thinking", "hungry", "eating")[j]))
except:
pass
. . .
- Note
- The presented strategy of intercepting the QS dictionary records #QS_OBJ_DICT and #QS_FUN_DICT is quite generic and can be easily adapted in your own customization scripts.
QView Script Commands
Here is the summary of commands that you can use in the QView scripts:
- reset_target()
- command()
- peek()
- poke()
- tick()
- glb_filter()
- loc_filter()
- ao_filter()
- current_obj()
- query_curr()
- post()
- publish()
- init()
- dispatch()
- qunpack().
QView™ Visualization & MonitoringQWin™ GUI Prototyping Toolkit