def _write(self, byte_msg: bytearray) -> None: try: self.ser.write(byte_msg) except serial.PortNotOpenError as error: raise can.CanOperationError("writing to closed port") from error except serial.SerialTimeoutException as error: raise can.CanTimeoutError() from error
def _send_once(self, data: bytes, channel: Optional[str] = None) -> int: try: if self.channel == "" and channel: # Message must be addressed to a specific channel sent = self.socket.sendto(data, (channel, )) else: sent = self.socket.send(data) except socket.error as error: raise can.CanOperationError( f"Failed to transmit: {error.strerror}", error.errno) return sent
def _check_bcm_task(self) -> None: # Do a TX_READ on a task ID, and check if we get EINVAL. If so, # then we are referring to a CAN message with the existing ID check_header = build_bcm_header( opcode=CAN_BCM_TX_READ, flags=0, count=0, ival1_seconds=0, ival1_usec=0, ival2_seconds=0, ival2_usec=0, can_id=self.task_id, nframes=0, ) try: self.bcm_socket.send(check_header) except OSError as error: if error.errno != errno.EINVAL: raise can.CanOperationError("failed to check", error.errno) from error else: raise can.CanOperationError( f"A periodic task for task ID {self.task_id} is already in progress " "by the SocketCAN Linux layer")
def send_bcm(bcm_socket: socket.socket, data: bytes) -> int: """ Send raw frame to a BCM socket and handle errors. """ try: return bcm_socket.send(data) except OSError as error: base = f"Couldn't send CAN BCM frame due to OS Error: {error.strerror}" if error.errno == errno.EINVAL: specific_message = " You are probably referring to a non-existing frame." elif error.errno == errno.ENETDOWN: specific_message = " The CAN interface appears to be down." elif error.errno == errno.EBADF: specific_message = " The CAN socket appears to be closed." else: specific_message = "" raise can.CanOperationError(base + specific_message, error.errno) from error
def _recv_internal( self, timeout: Optional[float]) -> Tuple[Optional[Message], bool]: try: # get all sockets that are ready (can be a list with a single value # being self.socket or an empty list if self.socket is not ready) ready_receive_sockets, _, _ = select.select([self.socket], [], [], timeout) except socket.error as error: # something bad happened (e.g. the interface went down) raise can.CanOperationError(f"Failed to receive: {error.strerror}", error.errno) if ready_receive_sockets: # not empty get_channel = self.channel == "" msg = capture_message(self.socket, get_channel) if msg and not msg.channel and self.channel: # Default to our own channel msg.channel = self.channel return msg, self._is_filtered # socket wasn't readable or timeout occurred return None, self._is_filtered
def send(self, msg: Message, timeout: Optional[float] = None) -> None: """Transmit a message to the CAN bus. :param msg: A message object. :param timeout: Wait up to this many seconds for the transmit queue to be ready. If not given, the call may fail immediately. :raises can.CanError: if the message could not be written. """ log.debug("We've been asked to write a message to the bus") logger_tx = log.getChild("tx") logger_tx.debug("sending: %s", msg) started = time.time() # If no timeout is given, poll for availability if timeout is None: timeout = 0 time_left = timeout data = build_can_frame(msg) while time_left >= 0: # Wait for write availability ready = select.select([], [self.socket], [], time_left)[1] if not ready: # Timeout break channel = str(msg.channel) if msg.channel else None sent = self._send_once(data, channel) if sent == len(data): return # Not all data were sent, try again with remaining data data = data[sent:] time_left = timeout - (time.time() - started) raise can.CanOperationError("Transmit buffer full")
def capture_message(sock: socket.socket, get_channel: bool = False) -> Optional[Message]: """ Captures a message from given socket. :param sock: The socket to read a message from. :param get_channel: Find out which channel the message comes from. :return: The received message, or None on failure. """ # Fetching the Arb ID, DLC and Data try: cf, ancillary_data, msg_flags, addr = sock.recvmsg( CANFD_MTU, RECEIVED_ANCILLARY_BUFFER_SIZE) if get_channel: channel = addr[0] if isinstance(addr, tuple) else addr else: channel = None except socket.error as error: raise can.CanOperationError(f"Error receiving: {error.strerror}", error.errno) can_id, can_dlc, flags, data = dissect_can_frame(cf) # Fetching the timestamp assert len(ancillary_data) == 1, "only requested a single extra field" cmsg_level, cmsg_type, cmsg_data = ancillary_data[0] assert (cmsg_level == socket.SOL_SOCKET and cmsg_type == SO_TIMESTAMPNS ), "received control message type that was not requested" # see https://man7.org/linux/man-pages/man3/timespec.3.html -> struct timespec for details seconds, nanoseconds = RECEIVED_TIMESTAMP_STRUCT.unpack_from(cmsg_data) if nanoseconds >= 1e9: raise can.CanOperationError( f"Timestamp nanoseconds field was out of range: {nanoseconds} not less than 1e9" ) timestamp = seconds + nanoseconds * 1e-9 # EXT, RTR, ERR flags -> boolean attributes # /* special address description flags for the CAN_ID */ # #define CAN_EFF_FLAG 0x80000000U /* EFF/SFF is set in the MSB */ # #define CAN_RTR_FLAG 0x40000000U /* remote transmission request */ # #define CAN_ERR_FLAG 0x20000000U /* error frame */ is_extended_frame_format = bool(can_id & CAN_EFF_FLAG) is_remote_transmission_request = bool(can_id & CAN_RTR_FLAG) is_error_frame = bool(can_id & CAN_ERR_FLAG) is_fd = len(cf) == CANFD_MTU bitrate_switch = bool(flags & CANFD_BRS) error_state_indicator = bool(flags & CANFD_ESI) # Section 4.7.1: MSG_DONTROUTE: set when the received frame was created on the local host. is_rx = not bool(msg_flags & socket.MSG_DONTROUTE) if is_extended_frame_format: # log.debug("CAN: Extended") # TODO does this depend on SFF or EFF? arbitration_id = can_id & 0x1FFFFFFF else: # log.debug("CAN: Standard") arbitration_id = can_id & 0x000007FF msg = Message( timestamp=timestamp, channel=channel, arbitration_id=arbitration_id, is_extended_id=is_extended_frame_format, is_remote_frame=is_remote_transmission_request, is_error_frame=is_error_frame, is_fd=is_fd, is_rx=is_rx, bitrate_switch=bitrate_switch, error_state_indicator=error_state_indicator, dlc=can_dlc, data=data, ) return msg
def _recv_internal(self, timeout): """ Read a message from the serial device. :param timeout: .. warning:: This parameter will be ignored. The timeout value of the channel is used. :returns: Received message and False (because not filtering as taken place). :rtype: can.Message, bool """ try: # ser.read can return an empty string # or raise a SerialException rx_byte_1 = self.ser.read() except serial.PortNotOpenError as error: raise can.CanOperationError("reading from closed port") from error except serial.SerialException: return None, False if rx_byte_1 and ord(rx_byte_1) == 0xAA: try: rx_byte_2 = ord(self.ser.read()) time_stamp = time() if rx_byte_2 == 0x55: status = bytearray([0xAA, 0x55]) status += bytearray(self.ser.read(18)) logger.debug("status resp:\t%s", status.hex()) else: length = int(rx_byte_2 & 0x0F) is_extended = bool(rx_byte_2 & 0x20) is_remote = bool(rx_byte_2 & 0x10) if is_extended: s_3_4_5_6 = bytearray(self.ser.read(4)) arb_id = (struct.unpack("<I", s_3_4_5_6))[0] else: s_3_4 = bytearray(self.ser.read(2)) arb_id = (struct.unpack("<H", s_3_4))[0] data = bytearray(self.ser.read(length)) end_packet = ord(self.ser.read()) if end_packet == 0x55: msg = Message( timestamp=time_stamp, arbitration_id=arb_id, is_extended_id=is_extended, is_remote_frame=is_remote, dlc=length, data=data, ) logger.debug("recv message: %s", str(msg)) return msg, False else: return None, False except serial.PortNotOpenError as error: raise can.CanOperationError( "reading from closed port") from error except serial.SerialException as error: raise can.CanOperationError( "failed to read message information") from error return None, None