diff --git a/can/bit_timing.py b/can/bit_timing.py index f5d50eac1..299e61529 100644 --- a/can/bit_timing.py +++ b/can/bit_timing.py @@ -78,6 +78,9 @@ def __init__( self._restrict_to_minimum_range() def _validate(self) -> None: + """ + Validates CAN bus nominal timing parameters. + """ if not 1 <= self.brp <= 64: raise ValueError(f"bitrate prescaler (={self.brp}) must be in [1...64].") @@ -105,6 +108,10 @@ def _validate(self) -> None: raise ValueError("nof_samples must be 1 or 3") def _restrict_to_minimum_range(self) -> None: + """ + Restricts and error checks for nominal bit time, bitrate prescaler + and bit range to the specified ranges. + """ if not 8 <= self.nbt <= 25: raise ValueError(f"nominal bit time (={self.nbt}) must be in [8...25].") @@ -584,6 +591,9 @@ def __init__( # pylint: disable=too-many-arguments self._restrict_to_minimum_range() def _validate(self) -> None: + """ + Validates CAN bus nominal timing parameters. + """ for param, value in self._data.items(): if value < 0: # type: ignore[operator] err_msg = f"'{param}' (={value}) must not be negative." @@ -630,6 +640,11 @@ def _validate(self) -> None: ) def _restrict_to_minimum_range(self) -> None: + """ + Restricts and error checks for nominal bit time, time segment 1 and 2 + for both nominal and data phases, and synchronization jump width for both + nominal and data pahses to ranges as specified in ISO 11898. + """ # restrict to minimum required range as defined in ISO 11898 if not 8 <= self.nbt <= 80: raise ValueError(f"Nominal bit time (={self.nbt}) must be in [8...80]") diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index a610b7a8a..43630fcdd 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -269,12 +269,18 @@ def __init__( self.start() def stop(self) -> None: + """ + Stops cyclic send task. + """ self.stopped = True if USE_WINDOWS_EVENTS: # Reset and signal any pending wait by setting the timer to 0 win32event.SetWaitableTimer(self.event.handle, 0, 0, None, None, False) def start(self) -> None: + """ + Starts cyclic send task using a daemon thread. + """ self.stopped = False if self.thread is None or not self.thread.is_alive(): name = f"Cyclic send task for 0x{self.messages[0].arbitration_id:X}" @@ -289,6 +295,10 @@ def start(self) -> None: self.thread.start() def _run(self) -> None: + """ + Sends messages on the bus periodically using a daemon thread. + Handles error conditions to maintain thread integrity. + """ msg_index = 0 msg_due_time_ns = time.perf_counter_ns() diff --git a/can/bus.py b/can/bus.py index 954c78c5f..0e26e2bdb 100644 --- a/can/bus.py +++ b/can/bus.py @@ -530,6 +530,9 @@ def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: raise NotImplementedError() def fileno(self) -> int: + """ Raises error which indicates fileno method is not implemented for + the current CAN bus + """ raise NotImplementedError("fileno is not implemented using current CAN bus") @@ -540,4 +543,7 @@ class _SelfRemovingCyclicTask(CyclicSendTaskABC, ABC): """ def stop(self, remove_task: bool = True) -> None: + """ + Stops cyclic task and raises not implemented error. + """ raise NotImplementedError() diff --git a/can/io/asc.py b/can/io/asc.py index 3a14f0a88..3eb2afd17 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -72,6 +72,14 @@ def __init__( self.internal_events_logged = False def _extract_header(self) -> None: + """ + Takes a file and reads line by line looking for header + information and parses that data out, setting it to the appropriate + variable. + + Comment lines will be skipped and parsing will be stopped after + necessary information is acquired. + """ for _line in self.file: line = _line.strip() diff --git a/can/io/canutils.py b/can/io/canutils.py index d7ae99daf..9907982b0 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -154,6 +154,11 @@ def __init__( self.last_timestamp = None def on_message_received(self, msg): + """ + Processes and logs incoming CAN messages in appropriate format. + + :param msg: Received CAN message. + """ # this is the case for the very first message: if self.last_timestamp is None: self.last_timestamp = msg.timestamp or 0.0 diff --git a/can/io/csv.py b/can/io/csv.py index 2abaeb70e..de3722ad1 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -113,6 +113,11 @@ def __init__( self.file.write("timestamp,arbitration_id,extended,remote,error,dlc,data\n") def on_message_received(self, msg: Message) -> None: + """ + Processes and logs CAN message in CSV format. + + :param msg: Received CAN message. + """ row = ",".join( [ repr(msg.timestamp), # cannot use str() here because that is rounding diff --git a/can/io/generic.py b/can/io/generic.py index 55468ff16..ad6f7c718 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -112,10 +112,12 @@ def file_size(self) -> int: class TextIOMessageWriter(FileIOMessageWriter, metaclass=ABCMeta): + """The base class for writing messages to a text file.""" file: TextIO class BinaryIOMessageWriter(FileIOMessageWriter, metaclass=ABCMeta): + """The base class for writing messages to a binary file.""" file: Union[BinaryIO, gzip.GzipFile] @@ -124,8 +126,10 @@ class MessageReader(BaseIOHandler, Iterable[Message], metaclass=ABCMeta): class TextIOMessageReader(MessageReader, metaclass=ABCMeta): + """The base class for reading messages from a text file.""" file: TextIO class BinaryIOMessageReader(MessageReader, metaclass=ABCMeta): + """The base class for reading messages from a binary file.""" file: Union[BinaryIO, gzip.GzipFile] diff --git a/can/io/logger.py b/can/io/logger.py index f54223741..ac2ff661c 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -67,6 +67,9 @@ def _update_writer_plugins() -> None: def _get_logger_for_suffix(suffix: str) -> Type[MessageWriter]: + """Retrieves logger class associated with file suffix. + + :param suffix: File suffix for logger.""" try: return MESSAGE_WRITERS[suffix] except KeyError: @@ -372,9 +375,17 @@ def __init__( @property def writer(self) -> FileIOMessageWriter: + """ + Returns current message wrtier. + """ return self._writer def should_rollover(self, msg: Message) -> bool: + """ + Determines if log file should be rolled over. + + :param msg: Incoming message for determining rollover. + """ if self.max_bytes <= 0: return False @@ -384,6 +395,9 @@ def should_rollover(self, msg: Message) -> bool: return False def do_rollover(self) -> None: + """ + Perform log rollover. + """ if self.writer: self.writer.stop() diff --git a/can/io/mf4.py b/can/io/mf4.py index 042bf8765..0e6189642 100644 --- a/can/io/mf4.py +++ b/can/io/mf4.py @@ -183,11 +183,15 @@ def file_size(self) -> int: return cast(int, self._mdf._tempfile.tell()) # pylint: disable=protected-access def stop(self) -> None: + """Stops CAN bus logging process and saved recorded data.""" self._mdf.save(self.file, compression=self._compression_level) self._mdf.close() super().stop() def on_message_received(self, msg: Message) -> None: + """Processes and logs CAN messsage based on its type + :param msg: CAN message received + """ channel = channel2int(msg.channel) timestamp = msg.timestamp @@ -481,5 +485,6 @@ def __iter__(self) -> Generator[Message, None, None]: self.stop() def stop(self) -> None: + """Stops CAN logging and closes MDF file.""" self._mdf.close() super().stop() diff --git a/can/io/printer.py b/can/io/printer.py index 67c353cc6..16a623710 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -43,6 +43,10 @@ def __init__( super().__init__(file, mode=mode) def on_message_received(self, msg: Message) -> None: + """Receives CAN message and either writes message to file or + prints message to console. + :param msg: CAN message received. + """ if self.write_to_file: cast(TextIO, self.file).write(str(msg) + "\n") else: diff --git a/can/io/sqlite.py b/can/io/sqlite.py index 43fd761e9..c9ff450f4 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -59,6 +59,13 @@ def __iter__(self) -> Generator[Message, None, None]: @staticmethod def _assemble_message(frame_data): + """ + Assembles CAN message from given frame data. + + :param frame_data: Frame containing data for timestamp information, + CAN ID, if CAN ID is extended, if message is remote frame, if message + is an error frame, the dlc of the message, and the payload of the message. + """ timestamp, can_id, is_extended, is_remote, is_error, dlc, data = frame_data return Message( timestamp=timestamp, @@ -192,6 +199,9 @@ def _create_db(self): self._conn.commit() def _db_writer_thread(self): + """ + Writes buffered CAN messages to database in a separate thread. + """ self._create_db() try: diff --git a/can/listener.py b/can/listener.py index 1f19c3acd..283f0192a 100644 --- a/can/listener.py +++ b/can/listener.py @@ -65,6 +65,11 @@ def __init__(self, bus: BusABC, *args: Any, **kwargs: Any) -> None: self.bus = bus def on_message_received(self, msg: Message) -> None: + """ + Sends received message to CAN bus. + + :param msg: Message object to send. + """ self.bus.send(msg) @@ -176,4 +181,7 @@ async def __anext__(self) -> Message: return await self.buffer.get() def stop(self) -> None: + """ + Stops buffer from accepting new messages. + """ self._is_stopped = True diff --git a/can/logconvert.py b/can/logconvert.py index 49cdaf4bb..0dc692cd8 100644 --- a/can/logconvert.py +++ b/can/logconvert.py @@ -10,6 +10,9 @@ class ArgumentParser(argparse.ArgumentParser): + """ + Prints help message and exits with error status. + """ def error(self, message): self.print_help(sys.stderr) self.exit(errno.EINVAL, f"{self.prog}: error: {message}\n") diff --git a/can/logger.py b/can/logger.py index 35c3db20b..7dbd00bda 100644 --- a/can/logger.py +++ b/can/logger.py @@ -95,6 +95,11 @@ def _append_filter_argument( def _create_bus(parsed_args: argparse.Namespace, **kwargs: Any) -> can.BusABC: + """ + Creates and configures a CAN bus based on the passed arguments. + + :param parsed_args: Parsed arguments. + """ logging_level_names = ["critical", "error", "warning", "info", "debug", "subdebug"] can.set_logging_level(logging_level_names[min(5, parsed_args.verbosity)]) @@ -114,6 +119,9 @@ def _create_bus(parsed_args: argparse.Namespace, **kwargs: Any) -> can.BusABC: class _CanFilterAction(argparse.Action): + """ + Argparse action for handling CAN filter arguments. + """ def __call__( self, parser: argparse.ArgumentParser, @@ -144,11 +152,22 @@ def __call__( def _parse_additional_config(unknown_args: Sequence[str]) -> TAdditionalCliArgs: + """ + Parses additional arguments from a sequence of unknown arguements. + + :param unknown_args: Unknown arguments. + """ for arg in unknown_args: if not re.match(r"^--[a-zA-Z\-]*?=\S*?$", arg): raise ValueError(f"Parsing argument {arg} failed") def _split_arg(_arg: str) -> Tuple[str, str]: + """ + Helper function to split arguments in --key=value format + and returns as tuple with the newly formatted key_value. + + :param _arg: Argument to be split + """ left, right = _arg.split("=", 1) return left.lstrip("-").replace("-", "_"), right diff --git a/can/notifier.py b/can/notifier.py index 34fcf74fa..06a840a06 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -109,6 +109,10 @@ def stop(self, timeout: float = 5) -> None: listener.stop() def _rx_thread(self, bus: BusABC) -> None: + """Receives messages from CAN Bus and processes them. + + :param bus: CAN bus from which messages are received. + """ # determine message handling callable early, not inside while loop handle_message = cast( Callable[[Message], None], @@ -140,10 +144,20 @@ def _rx_thread(self, bus: BusABC) -> None: logger.debug("suppressed exception: %s", exc) def _on_message_available(self, bus: BusABC) -> None: + """Processes available messages on CAN bus as soon as it is + received. + + : param bus: CAN bus from which messages are received. + """ if msg := bus.recv(0): self._on_message_received(msg) def _on_message_received(self, msg: Message) -> None: + """Handles received CAN message by notifying registered listeners. If + the listener returns a coroutine, event loop will be scheduled to run. + + :param msg: CAN message received. + """ for callback in self.listeners: res = callback(msg) if res and self._loop and asyncio.iscoroutine(res): diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index 9b008667f..b08f27d37 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -49,12 +49,23 @@ def __init__(self, *args, **kwargs): def recv( self, timeout=None, *args, **kwargs ): # pylint: disable=keyword-arg-before-vararg + """ + Receives message from CAN bus. + + :param timeout: Timeout for receiving message. + """ with self._lock_recv: return self.__wrapped__.recv(timeout=timeout, *args, **kwargs) def send( self, msg, timeout=None, *args, **kwargs ): # pylint: disable=keyword-arg-before-vararg + """ + Sends a message to the CAN bus. + + :param msg: Message to be sent on CAN bus. + :param timeout: Timeout for sending message. + """ with self._lock_send: return self.__wrapped__.send(msg, timeout=timeout, *args, **kwargs) @@ -63,34 +74,60 @@ def send( @property def filters(self): + """ + Receives filters on the CAN bus. + """ with self._lock_recv: return self.__wrapped__.filters @filters.setter def filters(self, filters): + """ + Sets filters on the CAN bus. + + :param filters: Filters to set. + """ with self._lock_recv: self.__wrapped__.filters = filters def set_filters( self, filters=None, *args, **kwargs ): # pylint: disable=keyword-arg-before-vararg + """ + Sets filters on the CAN bus. + Takes additional arguments and key word arguments. + + :param filters: Filters to set. + """ with self._lock_recv: return self.__wrapped__.set_filters(filters=filters, *args, **kwargs) def flush_tx_buffer(self, *args, **kwargs): + """ + Flushes the buffer of the CAN bus. + """ with self._lock_send: return self.__wrapped__.flush_tx_buffer(*args, **kwargs) def shutdown(self, *args, **kwargs): + """ + Shuts down the CAN bus. + """ with self._lock_send, self._lock_recv: return self.__wrapped__.shutdown(*args, **kwargs) @property def state(self): + """ + Retrieves the current state of the CAN bus. + """ with self._lock_send, self._lock_recv: return self.__wrapped__.state @state.setter def state(self, new_state): + """ + Sets the current state of the CAN bus. + """ with self._lock_send, self._lock_recv: self.__wrapped__.state = new_state diff --git a/can/typechecking.py b/can/typechecking.py index 284dd8aba..5e0b36a97 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -17,11 +17,24 @@ class CanFilter(typing.TypedDict): + """ + CAN filter configuration. + + :param can_id: CAN ID to filter. + :param can_mask: CAN mask to apply. + """ can_id: int can_mask: int class CanFilterExtended(typing.TypedDict): + """ + Extended CAN filter configuration. + + :param can_id: CAN ID to filter. + :param can_mask: CAN mask to apply. + :param extended: Indicates if filter is for extended CAN. + """ can_id: int can_mask: int extended: bool @@ -57,6 +70,12 @@ class CanFilterExtended(typing.TypedDict): class AutoDetectedConfig(typing.TypedDict): + """ + Auto-detected CAN interface configuration. + + :param interface: Name of CAN interface. + : param channel: Channel on CAN interface. + """ interface: str channel: Channel @@ -65,6 +84,16 @@ class AutoDetectedConfig(typing.TypedDict): class BitTimingDict(typing.TypedDict): + """ + Bit timing CAN configuration. + + :param f_clock: Frequency of CAN controller clock. + :param brp: Baud rate prescaler. + :param tseg1: Time segment 1. + :param tseg2: Time segment 2. + :param sjw: Synchronization jump width. + :param nof_samples: Number of samples. + """ f_clock: int brp: int tseg1: int @@ -74,6 +103,19 @@ class BitTimingDict(typing.TypedDict): class BitTimingFdDict(typing.TypedDict): + """ + Bit timing CAN FD configuration. + + :param f_clock: Frequency of CAN controller clock. + :param brp: Baud rate prescaler. + :param nom_tseg1: Nominal time segment 1. + :param nom_tseg2: Nominal time segment 2. + :param nom_sjw: Nominal synchronization jump width. + :param data_brp: Data phase baud rate prescaler. + :param data_tseg1: Data time segment 1. + :param data_tseg2: Data time segment 2. + :param data_sjw: Data synchronization jump width. + """ f_clock: int nom_brp: int nom_tseg1: int diff --git a/can/viewer.py b/can/viewer.py index 45c313b07..7679be0b3 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -53,6 +53,14 @@ class CanViewer: # pylint: disable=too-many-instance-attributes + """ + Terminal-based CAN message viewer. + + :param stdscr: Main window. + :param bus: CAN bus receiving messages. + :param data_structs: Data structures for formatting messages. + :param testing: Indicates if class is initialized for testing. + """ def __init__(self, stdscr, bus, data_structs, testing=False): self.stdscr = stdscr self.bus = bus @@ -85,6 +93,9 @@ def __init__(self, stdscr, bus, data_structs, testing=False): self.run() def run(self): + """ + Main loop for CAN bus viewer. + """ # Clear the terminal and draw the header self.draw_header() @@ -162,6 +173,13 @@ def run(self): # Unpack the data and then convert it into SI-units @staticmethod def unpack_data(cmd: int, cmd_to_struct: Dict, data: bytes) -> List[float]: + """ + Unpacks data from byte stream and converts it into SI units. + + :param cmd: Command indentifier that determines structure and conversion of data. + :param cmd_to_struct: Dictionary mapping command to corresponding struct. + :param data: Byte stream with data to be unpacked. + """ if not cmd_to_struct or not data: # These messages do not contain a data package return [] @@ -188,6 +206,12 @@ def unpack_data(cmd: int, cmd_to_struct: Dict, data: bytes) -> List[float]: raise ValueError(f"Unknown command: 0x{cmd:02X}") def draw_can_bus_message(self, msg, sorting=False): + """ + Draws CAN bus message to terminal interface. + + :param msg: CAN bus message to be displayed. + :param sorting: Flag to indicate if messages should be sorted. + """ # Use the CAN-Bus ID as the key in the dict key = msg.arbitration_id @@ -312,6 +336,13 @@ def draw_can_bus_message(self, msg, sorting=False): return self.ids[key] def draw_line(self, row, col, txt, *args): + """ + Draws line of text to terminal screen to specified row and column. + + :param row: Row where text is to be drawn to. + :param col: Column where text is to be drawn to. + :param txt: Text to be drawn. + """ if row - self.scroll < 0: # Skip if we have scrolled past the line return @@ -323,6 +354,9 @@ def draw_line(self, row, col, txt, *args): pass def draw_header(self): + """ + Draws header to terminal screen. + """ self.stdscr.erase() self.draw_line(0, 0, "Count", curses.A_BOLD) self.draw_line(0, 8, "Time", curses.A_BOLD) @@ -339,6 +373,9 @@ def draw_header(self): self.draw_line(0, 77, "Parsed values", curses.A_BOLD) def redraw_screen(self): + """ + Redraws the entire terminal screen. + """ # Trigger a complete redraw self.draw_header() for ids in self.ids.values(): @@ -346,14 +383,32 @@ def redraw_screen(self): class SmartFormatter(argparse.HelpFormatter): + """ + Custom argument formatter for argparse. + """ def _get_default_metavar_for_optional(self, action): + """ + Returns default metavar for optional arguments. + + :param action: Action for metvar being determined. + """ return action.dest.upper() def _format_usage(self, usage, actions, groups, prefix): + """ + Formats usage message. + + :param usage: Usage message. + :param actions: Actions for the parser. + :param groups: Mutually exclusive groups. + :param prefix: Prefix for usage message. + """ # Use uppercase for "Usage:" text return super()._format_usage(usage, actions, groups, "Usage: ") def _format_args(self, action, default_metavar): + """ + """ if action.nargs not in (argparse.REMAINDER, argparse.ONE_OR_MORE): return super()._format_args(action, default_metavar) @@ -362,6 +417,11 @@ def _format_args(self, action, default_metavar): return str(get_metavar(1)) def _format_action_invocation(self, action): + """ + Formats invocation string for an action. + + :param action: Action for invocation string to be formatted. + """ if not action.option_strings or action.nargs == 0: return super()._format_action_invocation(action) @@ -378,12 +438,25 @@ def _format_action_invocation(self, action): return ", ".join(parts) def _split_lines(self, text, width): + """ + Splits text into lines with specified width. + + :param text: Text to be split. + :param width: Max width of lines. + """ # Allow to manually split the lines if text.startswith("R|"): return text[2:].splitlines() return super()._split_lines(text, width) def _fill_text(self, text, width, indent): + """ + Fills text with given width and indent. + + :param text: Text to be filled. + :param width: Max width of line. + :param indent: Indentation for each line. + """ if text.startswith("R|"): return "".join(indent + line + "\n" for line in text[2:].splitlines()) else: @@ -393,6 +466,11 @@ def _fill_text(self, text, width, indent): def _parse_viewer_args( args: List[str], ) -> Tuple[argparse.Namespace, TDataStructs, TAdditionalCliArgs]: + """ + Parses command-line arguments for CAN viewer. + + :param args: Command-line arguments to be parsed. + """ # Parse command line arguments parser = argparse.ArgumentParser( "python -m can.viewer", diff --git a/examples/cyclic_checksum.py b/examples/cyclic_checksum.py index 3ab6c78ac..56c2349cb 100644 --- a/examples/cyclic_checksum.py +++ b/examples/cyclic_checksum.py @@ -32,12 +32,18 @@ def cyclic_checksum_send(bus: can.BusABC) -> None: def update_message(message: can.Message) -> None: + """ + Updates given CAN message with a new counter and checksum. + """ counter = increment_counter(message) checksum = compute_xbr_checksum(message, counter) message.data[7] = (checksum << 4) + counter def increment_counter(message: can.Message) -> int: + """ + Increments 4-bit counter for given CAN message. + """ counter = message.data[7] & 0x0F counter += 1 counter %= 16