Example #1
0
 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
Example #2
0
 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
Example #3
0
 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")
Example #4
0
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
Example #5
0
    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
Example #6
0
    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")
Example #7
0
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
Example #8
0
    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