1515import can
1616import curses
1717import os
18- import six
1918import struct
2019import sys
2120import 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' )
0 commit comments