Skip to content

Add changed byte highlighting to viewer.py #1159

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Nov 16, 2021
52 changes: 49 additions & 3 deletions can/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,14 @@ def __init__(self, stdscr, bus, data_structs, testing=False):
self.bus = bus
self.data_structs = data_structs

# Initialise the ID dictionary, start timestamp, scroll and variable for pausing the viewer
# Initialise the ID dictionary, Previous values dict, start timestamp,
# scroll and variables for pausing the viewer and enabling byte highlighting
self.ids = {}
self.start_time = None
self.scroll = 0
self.paused = False
self.highlight_changed_bytes = False
self.previous_values = {}

# Get the window dimensions - used for resizing the window
self.y, self.x = self.stdscr.getmaxyx()
Expand All @@ -70,6 +73,8 @@ def __init__(self, stdscr, bus, data_structs, testing=False):

# Used to color error frames red
curses.init_pair(1, curses.COLOR_RED, -1)
# Used to color changed bytes
curses.init_pair(2, curses.COLOR_CYAN, curses.COLOR_BLUE)

if not testing: # pragma: no cover
self.run()
Expand Down Expand Up @@ -103,6 +108,11 @@ def run(self):
self.scroll = 0
self.draw_header()

# Toggle byte change highlighting pressing 'h'
elif key == ord("h"):
self.highlight_changed_bytes = not self.highlight_changed_bytes
self.draw_header()

# Sort by pressing 's'
elif key == ord("s"):
# Sort frames based on the CAN-Bus ID
Expand Down Expand Up @@ -241,14 +251,36 @@ def draw_can_bus_message(self, msg, sorting=False):
)
self.draw_line(self.ids[key]["row"], 35, arbitration_id_string, color)
self.draw_line(self.ids[key]["row"], 47, str(msg.dlc), color)

try:
previous_byte_values = self.previous_values[key]
except KeyError: # no row of previous values exists for the current message ID
# initialise a row to store the values for comparison next time
self.previous_values[key] = dict()
previous_byte_values = self.previous_values[key]
for i, b in enumerate(msg.data):
col = 52 + i * 3
if col > self.x - 2:
# Data does not fit
self.draw_line(self.ids[key]["row"], col - 4, "...", color)
break
if self.highlight_changed_bytes:
try:
if b != previous_byte_values[i]:
# set colour to highlight a changed value
data_color = curses.color_pair(2)
else:
data_color = color
except KeyError:
# previous entry for byte didnt exist - default to rest of line colour
data_color = color
finally:
# write the new value to the previous values dict for next time
previous_byte_values[i] = b
else:
data_color = color
text = "{:02X}".format(b)
self.draw_line(self.ids[key]["row"], col, text, color)
self.draw_line(self.ids[key]["row"], col, text, data_color)

if self.data_structs:
try:
Expand Down Expand Up @@ -286,7 +318,12 @@ def draw_header(self):
self.draw_line(0, 35, "ID", curses.A_BOLD)
self.draw_line(0, 47, "DLC", curses.A_BOLD)
self.draw_line(0, 52, "Data", curses.A_BOLD)
if self.data_structs: # Only draw if the dictionary is not empty

# Indicate that byte change highlighting is enabled
if self.highlight_changed_bytes:
self.draw_line(0, 57, "(changed)", curses.color_pair(2))
# Only draw if the dictionary is not empty
if self.data_structs:
self.draw_line(0, 77, "Parsed values", curses.A_BOLD)

def redraw_screen(self):
Expand Down Expand Up @@ -353,6 +390,7 @@ def parse_args(args):
"\n | ESQ/q | Exit the viewer |"
"\n | c | Clear the stored frames |"
"\n | s | Sort the stored frames |"
"\n | h | Toggle byte highlights |"
"\n | SPACE | Pause the viewer |"
"\n | UP/DOWN | Scroll the viewer |"
"\n +---------+-------------------------+",
Expand Down Expand Up @@ -460,6 +498,14 @@ def parse_args(args):
default=2,
)

# suppressed as the user doesn't need to change it
optional.add_argument(
"--app_name",
dest="app_name",
help=argparse.SUPPRESS,
default="python-can-viewer",
)

# Print help message when no arguments are given
if not args:
parser.print_help(sys.stderr)
Expand Down
Binary file added doc/images/viewer_changed_bytes_highlighting.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 15 additions & 1 deletion doc/scripts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,21 @@ A screenshot of the application can be seen below:
.. image:: images/viewer.png
:width: 100%

The first column is the number of times a frame with the particular ID that has been received, next is the timestamp of the frame relative to the first received message. The third column is the time between the current frame relative to the previous one. Next is the length of the frame, the data and then the decoded data converted according to the ``-d`` argument. The top red row indicates an error frame.
The first column is the number of times a frame with the particular ID that has been received, next is the timestamp of the frame relative to the first received message. The third column is the time between the current frame relative to the previous one. Next is the length of the frame, the data and then the decoded data converted according to the ``-d`` argument. The top red row indicates an error frame.
There are several keyboard shortcuts that can be used with the viewer script, they function as follows:

* ESCAPE - Quit the viewer script
* q - as ESCAPE
* c - Clear the stored frames
* s - Sort the stored frames
* h - Toggle highlighting of changed bytes in the data field - see the below image
* SPACE - Pause the viewer
* UP/DOWN - Scroll the viewer

.. image:: images/viewer_changed_bytes_highlighting.png
:width: 50%

A byte in the data field is highlighted blue if the value is different from the last time the message was received.

Command line arguments
^^^^^^^^^^^^^^^^^^^^^^
Expand Down