QTools 6.9.0
Customizing & Extending QView™

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.

Remarks
You don't need to change the qview.py script to customize and extend it for your project. The customization you make go into a separate script, which is specific to your project. That way, you can have many different customization scripts and you don't pollute (and possibly break) the original code.

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 the 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):
# request target reset on startup...
# add commands to the Custom menu...
[4] QView.custom_menu.add_command(label="Custom command",
command=self.cust_command)
# configure the custom QView.canvas...
[5] QView.show_canvas() # make the canvas visible
[6] QView.canvas.configure(width=400, height=260)
# tuple of activity images (correspond to self._philo_state)
[7] self._act_img = (
[8] PhotoImage(file=HOME_DIR + "/img/thinking.gif"),
PhotoImage(file=HOME_DIR + "/img/hungry.gif"),
PhotoImage(file=HOME_DIR + "/img/eating.gif"),
)
# tuple of philo canvas images (correspond to self._philo_obj)
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])
)
# button images for UP and DOWN
self.img_UP = PhotoImage(file=HOME_DIR + "/img/BTN_UP.gif")
self.img_DWN = PhotoImage(file=HOME_DIR + "/img/BTN_DWN.gif")
# images of a button for pause/serve
self.btn = QView.canvas.create_image(200, 120, image=self.img_UP)
[9] QView.canvas.tag_bind(self.btn, "<ButtonPress-1>", self.cust_pause)
# on_reset() callback
[10] def on_reset(self):
# clear the lists
[11] self._philo_obj = [0, 0, 0, 0, 0]
self._philo_state = [0, 0, 0]
# on_run() callback
[20] def on_run(self):
[21] glb_filter("QS_USER_00")
# NOTE: the names of objects for loc_filter() and current_obj()
# must match the QS Object Dictionaries produced by the application.
[22] current_obj(OBJ_AO, "Table_inst")
[23] loc_filter(OBJ_TE, "l_philo[0].timeEvt")
# turn lists into tuples for better performance
[24] self._philo_obj = tuple(self._philo_obj)
self._philo_state = tuple(self._philo_state)
# example of a custom command
[30] def cust_command(self):
[31] command(1, 12345)
# example of a custom interaction with a canvas object (pause/serve)
[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")
# intercept the QS_USER_00 application-specific packet
# this packet has the following structure (see bsp.c:displayPhilStat()):
# record-ID, seq-num, Timestamp, format-byte, Philo-num,
# format-bye, Zero-terminated string (status)
[50] def QS_USER_00(self, packet):
# unpack: Timestamp->data[0], Philo-num->data[1], status->data[3]
[51] data = qunpack("xxTxBxZ", packet)
i = data[1]
j = ("t", "h", "e").index(data[2][0]) # the first letter
# animate the given philo image according to its activity
QView.canvas.itemconfig(self._philo_img[i], image=self._act_img[j])
# print a message to the text view
QView.print_text("%010d Philo %1d is %s"%(data[0], i, data[2]))
#=============================================================================
[60] QView.customize(DPP()) # set the QView customization

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 setup in the constructor of the customization clss __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 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_dict. 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 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 as in the QUTest. This commonality between QView™ and QUTest™ is intentional.

Custom Commands

The Custom Command added either to the QView.custom_menu or attached to the Canvas elements need to be implemented as a 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 as in the QUTest. This commonality between QView™ and QUTest™ 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 form 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 the 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 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 abou 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:
. . .
# intercept the QS_OBJ_DICT stadard packet
# this packet has the following structure:
# record-ID, seq-num, Object-ptr, Zero-terminated string
def QS_OBJ_DICT(self, packet):
data = qunpack("xxOZ", packet)
try:
# NOTE: the names of objects must match the QS Object Dictionaries
# produced by the application.
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 # dictionary for a different object
# intercept the QS_FUN_DICT stadard packet
# this packet has the following structure:
# record-ID, seq-num, Function-ptr, Zero-terminated string
def QS_FUN_DICT(self, packet):
data = qunpack("xxFZ", packet)
try:
# NOTE: the names of states must match the QS Object Dictionaries
# produced by the application.
j = ("Philo_thinking",
"Philo_hungry",
"Philo_eating").index(data[1])
self._philo_state[j] = data[0]
except:
pass # dictionary for a different state
# intercept the QS_QEP_TRAN stadard packet
# this packet has the following structure:
# record-ID, seq-num, Timestamp, Signal, Object-ptr,
# Function-ptr (source state), Function-ptr (new active state)
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])
# animate the given philo image according to its activity
QView.canvas.itemconfig(self._philo_img[i],
image=self._act_img[j])
# print a message to the text view
QView.print_text("%010d Philo %d is %s"\
%(data[0], i, ("thinking", "hungry", "eating")[j]))
except:
pass # state-entry in a different object
. . .


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.

Next: QWin™ GUI Prototyping Toolkit

qview.glb_filter
def glb_filter(*args)
Set/clear the Global-Filter in the Target.
Definition: qview.py:1492
QS_OBJ_DICT
@ QS_OBJ_DICT
object dictionary entry
Definition: qs_copy.h:153
qview.current_obj
def current_obj(obj_kind, obj_id)
Set the Current-Object in the Target.
Definition: qview.py:1575
qview.post
def post(signal, params=None)
post a given event to the current AO object in the Target
Definition: qview.py:1602
qutest_dsl.on_reset
def on_reset()
callback function invoked after each Target reset
Definition: qutest_dsl.py:456
QS_FUN_DICT
@ QS_FUN_DICT
function dictionary entry
Definition: qs_copy.h:154
qview.reset_target
def reset_target(*args)
Send the RESET packet to the Target.
Definition: qview.py:1457
qview.loc_filter
def loc_filter(obj_kind, obj_id)
Set the Local Filter in the Target.
Definition: qview.py:1558
qview.qunpack
def qunpack(fmt, bstr)
Unpack a QS trace record.
Definition: qview.py:1646
QS_QEP_TRAN
@ QS_QEP_TRAN
a regular transition was taken
Definition: qs_copy.h:72
qview.command
def command(cmdId, param1=0, param2=0, param3=0)
executes a given command in the Target
Definition: qview.py:1465