Skip to content

Commit 33ffd6c

Browse files
committed
Color error frames red, set timeout in a better way so frames are never dropped, sort frame by pressing 's' instead of doing it automatically and moved "pack_data" into the test
1 parent 2e42612 commit 33ffd6c

4 files changed

Lines changed: 133 additions & 105 deletions

File tree

can/scripts/viewer.py

Lines changed: 64 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import can
1616
import curses
1717
import os
18-
import six
1918
import struct
2019
import sys
2120
import time
@@ -94,6 +93,9 @@ def __init__(self, stdscr, bus, data_structs, ignore_canopen, testing=False):
9493
curses.curs_set(0)
9594
curses.use_default_colors()
9695

96+
# Used to color error frames red
97+
curses.init_pair(1, curses.COLOR_RED, -1)
98+
9799
if not testing: # pragma: no cover
98100
self.run()
99101

@@ -105,9 +107,12 @@ def run(self):
105107
# Do not read the CAN-Bus when in paused mode
106108
if not self.paused:
107109
# Read the CAN-Bus and draw it in the terminal window
108-
msg = self.bus.recv(timeout=0)
110+
msg = self.bus.recv(timeout=1. / 1000.)
109111
if msg is not None:
110112
self.draw_can_bus_message(msg)
113+
else:
114+
# Sleep 1 ms, so the application does not use 100 % of the CPU resources
115+
time.sleep(1. / 1000.)
111116

112117
# Read the terminal input
113118
key = self.stdscr.getch()
@@ -123,6 +128,17 @@ def run(self):
123128
self.scroll = 0
124129
self.draw_header()
125130

131+
# Sort by pressing 's'
132+
elif key == ord('s'):
133+
# Sort frames based on the CAN-Bus ID
134+
self.draw_header()
135+
for i, key in enumerate(sorted(self.ids.keys())):
136+
# Set the new row index, but skip the header
137+
self.ids[key]['row'] = i + 1
138+
139+
# Do a recursive call, so the frames are repositioned
140+
self.draw_can_bus_message(self.ids[key]['msg'], sorting=True)
141+
126142
# Pause by pressing space
127143
elif key == KEY_SPACE:
128144
self.paused = not self.paused
@@ -147,51 +163,9 @@ def run(self):
147163
curses.resizeterm(self.y, self.x)
148164
self.redraw_screen()
149165

150-
# Sleep 0.1 ms, so the application does not use 100 % of the CPU resources
151-
time.sleep(.1 / 1000.)
152-
153166
# Shutdown the CAN-Bus interface
154167
self.bus.shutdown()
155168

156-
# Convert it into raw integer values and then pack the data
157-
@staticmethod
158-
def pack_data(cmd, cmd_to_struct, *args): # type: (int, Dict, Union[*float, *int]) -> bytes
159-
if not cmd_to_struct or len(args) == 0:
160-
# If no arguments are given, then the message does not contain a data package
161-
return b''
162-
163-
for key in cmd_to_struct.keys():
164-
if cmd == key if isinstance(key, int) else cmd in key:
165-
value = cmd_to_struct[key]
166-
if isinstance(value, tuple):
167-
# The struct is given as the fist argument
168-
struct_t = value[0] # type: struct.Struct
169-
170-
# The conversion from SI-units to raw values are given in the rest of the tuple
171-
fmt = struct_t.format
172-
if isinstance(fmt, six.string_types): # pragma: no cover
173-
# Needed for Python 3.7
174-
fmt = six.b(fmt)
175-
176-
# Make sure the endian is given as the first argument
177-
assert six.byte2int(fmt) == ord('<') or six.byte2int(fmt) == ord('>')
178-
179-
# Disable rounding if the format is a float
180-
data = []
181-
for c, arg, val in zip(six.iterbytes(fmt[1:]), args, value[1:]):
182-
if c == ord('f'):
183-
data.append(arg * val)
184-
else:
185-
data.append(round(arg * val))
186-
else:
187-
# No conversion from SI-units is needed
188-
struct_t = value # type: struct.Struct
189-
data = args
190-
191-
return struct_t.pack(*data)
192-
else:
193-
raise ValueError('Unknown command: 0x{:02X}'.format(cmd))
194-
195169
# Unpack the data and then convert it into SI-units
196170
@staticmethod
197171
def unpack_data(cmd, cmd_to_struct, data): # type: (int, Dict, bytes) -> List[Union[float, int]]
@@ -309,54 +283,51 @@ def draw_can_bus_message(self, msg, sorting=False):
309283
# Increment frame counter
310284
self.ids[key]['count'] += 1
311285

312-
# Sort frames based on the CAN-Bus ID if a new frame was added
313-
if new_id_added:
314-
self.draw_header()
315-
for i, key in enumerate(sorted(self.ids.keys())):
316-
# Set the new row index, but skip the header
317-
self.ids[key]['row'] = i + 1
286+
# Format the CAN-Bus ID as a hex value
287+
arbitration_id_string = '0x{0:0{1}X}'.format(msg.arbitration_id, 8 if msg.is_extended_id else 3)
318288

319-
# Do a recursive call, so the frames are repositioned
320-
self.draw_can_bus_message(self.ids[key]['msg'], sorting=True)
321-
else:
322-
# Format the CAN-Bus ID as a hex value
323-
arbitration_id_string = '0x{0:0{1}X}'.format(msg.arbitration_id, 8 if msg.is_extended_id else 3)
289+
# Generate data string
290+
data_string = ''
291+
if msg.dlc > 0:
292+
data_string = ' '.join('{:02X}'.format(x) for x in msg.data)
324293

325-
# Generate data string
326-
data_string = ''
327-
if msg.dlc > 0:
328-
data_string = ' '.join('{:02X}'.format(x) for x in msg.data)
294+
# Check if is a CANopen message
295+
if self.ignore_canopen:
296+
canopen_function_code_string, canopen_node_id_string = None, None
297+
else:
298+
canopen_function_code_string, canopen_node_id_string = self.parse_canopen_message(msg)
329299

330-
# Check if is a CANopen message
331-
if self.ignore_canopen:
332-
canopen_function_code_string, canopen_node_id_string = None, None
333-
else:
334-
canopen_function_code_string, canopen_node_id_string = self.parse_canopen_message(msg)
335-
336-
# Now draw the CAN-Bus message on the terminal window
337-
self.draw_line(self.ids[key]['row'], 0, str(self.ids[key]['count']))
338-
self.draw_line(self.ids[key]['row'], 8, '{0:.6f}'.format(self.ids[key]['msg'].timestamp - self.start_time))
339-
self.draw_line(self.ids[key]['row'], 23, '{0:.6f}'.format(self.ids[key]['dt']))
340-
self.draw_line(self.ids[key]['row'], 35, arbitration_id_string)
341-
self.draw_line(self.ids[key]['row'], 47, str(msg.dlc))
342-
self.draw_line(self.ids[key]['row'], 52, data_string)
343-
if canopen_function_code_string:
344-
self.draw_line(self.ids[key]['row'], 77, canopen_function_code_string)
345-
if canopen_node_id_string:
346-
self.draw_line(self.ids[key]['row'], 88, canopen_node_id_string)
347-
348-
if self.data_structs:
349-
try:
350-
values_list = []
351-
for x in self.unpack_data(msg.arbitration_id, self.data_structs, msg.data):
352-
if isinstance(x, float):
353-
values_list.append('{0:.6f}'.format(x))
354-
else:
355-
values_list.append(str(x))
356-
values_string = ' '.join(values_list)
357-
self.draw_line(self.ids[key]['row'], 97 - (20 if self.ignore_canopen else 0), values_string)
358-
except (ValueError, struct.error):
359-
pass
300+
# Use red for error frames
301+
if msg.is_error_frame:
302+
color = curses.color_pair(1)
303+
else:
304+
color = curses.color_pair(0)
305+
306+
# Now draw the CAN-Bus message on the terminal window
307+
self.draw_line(self.ids[key]['row'], 0, str(self.ids[key]['count']), color)
308+
self.draw_line(self.ids[key]['row'], 8, '{0:.6f}'.format(self.ids[key]['msg'].timestamp - self.start_time),
309+
color)
310+
self.draw_line(self.ids[key]['row'], 23, '{0:.6f}'.format(self.ids[key]['dt']), color)
311+
self.draw_line(self.ids[key]['row'], 35, arbitration_id_string, color)
312+
self.draw_line(self.ids[key]['row'], 47, str(msg.dlc), color)
313+
self.draw_line(self.ids[key]['row'], 52, data_string, color)
314+
if canopen_function_code_string:
315+
self.draw_line(self.ids[key]['row'], 77, canopen_function_code_string, color)
316+
if canopen_node_id_string:
317+
self.draw_line(self.ids[key]['row'], 88, canopen_node_id_string, color)
318+
319+
if self.data_structs:
320+
try:
321+
values_list = []
322+
for x in self.unpack_data(msg.arbitration_id, self.data_structs, msg.data):
323+
if isinstance(x, float):
324+
values_list.append('{0:.6f}'.format(x))
325+
else:
326+
values_list.append(str(x))
327+
values_string = ' '.join(values_list)
328+
self.draw_line(self.ids[key]['row'], 97 - (20 if self.ignore_canopen else 0), values_string, color)
329+
except (ValueError, struct.error):
330+
pass
360331

361332
return self.ids[key]
362333

@@ -455,6 +426,7 @@ def parse_args(args):
455426
'\n +---------+-------------------------+'
456427
'\n | ESQ/q | Exit the viewer |'
457428
'\n | c | Clear the stored frames |'
429+
'\n | s | Sort the stored frames |'
458430
'\n | SPACE | Pause the viewer |'
459431
'\n | UP/DOWN | Scroll the viewer |'
460432
'\n +---------+-------------------------+',
@@ -473,7 +445,7 @@ def parse_args(args):
473445
optional.add_argument('-c', '--channel', help='''Most backend interfaces require some sort of channel.
474446
For example with the serial interface the channel might be a rfcomm device: "/dev/rfcomm0"
475447
with the socketcan interfaces valid channel examples include: "can0", "vcan0".
476-
(default: use default for the specified interface)''', default=None)
448+
(default: use default for the specified interface)''')
477449

478450
optional.add_argument('-d', '--decode', dest='decode',
479451
help='R|Specify how to convert the raw bytes into real values.'
@@ -521,8 +493,8 @@ def parse_args(args):
521493
metavar='{<can_id>:<can_mask>,<can_id>~<can_mask>}', nargs=argparse.ONE_OR_MORE, default='')
522494

523495
optional.add_argument('-i', '--interface', dest='interface',
524-
help='R|Specify the backend CAN interface to use. (default: "socketcan")',
525-
choices=sorted(can.VALID_INTERFACES), default='socketcan')
496+
help='R|Specify the backend CAN interface to use.',
497+
choices=sorted(can.VALID_INTERFACES))
526498

527499
optional.add_argument('--ignore-canopen', dest='ignore_canopen', help='''Do not print CANopen information''',
528500
action='store_true')

doc/scripts.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ The full usage page can be seen below::
167167
python -m can.viewer -f 100:7FC
168168
Note that the ID and mask are alway interpreted as hex values
169169
-i, --interface {iscan,ixxat,kvaser,neovi,nican,pcan,serial,slcan,socketcan,socketcan_ctypes,socketcan_native,usb2can,vector,virtual}
170-
Specify the backend CAN interface to use. (default: "socketcan")
170+
Specify the backend CAN interface to use.
171171
--ignore-canopen Do not print CANopen information
172172

173173
Shortcuts:
@@ -176,6 +176,7 @@ The full usage page can be seen below::
176176
+---------+-------------------------+
177177
| ESQ/q | Exit the viewer |
178178
| c | Clear the stored frames |
179+
| s | Sort the stored frames |
179180
| SPACE | Pause the viewer |
180181
| UP/DOWN | Scroll the viewer |
181182
+---------+-------------------------+

setup.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
'pytest-timeout ~= 1.2',
3737
'pytest-cov ~= 2.5',
3838
'codecov ~= 2.0',
39-
'future'
39+
'future',
40+
'six'
4041
] + extras_require['serial']
4142

4243
extras_require['test'] = tests_require
@@ -99,7 +100,7 @@
99100
# see https://www.python.org/dev/peps/pep-0345/#version-specifiers
100101
python_requires=">=2.7,!=3.0,!=3.1,!=3.2,!=3.3",
101102
install_requires=[
102-
'wrapt ~= 1.10', 'six', 'typing', 'windows-curses;platform_system=="Windows"',
103+
'wrapt ~= 1.10', 'typing', 'windows-curses;platform_system=="Windows"',
103104
],
104105
extras_require=extras_require,
105106

0 commit comments

Comments
 (0)