Esempio n. 1
0
    def make_input_socket(
            self,
            data_specifier: pyuavcan.transport.DataSpecifier) -> socket.socket:
        _logger.debug("%r: Constructing new input socket for %s", self,
                      data_specifier)
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
                          socket.IPPROTO_UDP)
        s.setblocking(False)
        # Allow other applications to use the same UAVCAN port as well.
        # These options shall be set before the socket is bound.
        # https://stackoverflow.com/questions/14388706/how-do-so-reuseaddr-and-so-reuseport-differ/14388707#14388707
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        if sys.platform.startswith("linux"):  # pragma: no branch
            # This is expected to be useful for unicast inputs only.
            # https://stackoverflow.com/a/14388707/1007777
            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)

        if isinstance(data_specifier, MessageDataSpecifier):
            multicast_ip = message_data_specifier_to_multicast_group(
                self._local, data_specifier)
            multicast_port = SUBJECT_PORT
            if sys.platform.startswith("linux"):
                # Binding to the multicast group address is necessary on GNU/Linux: https://habr.com/ru/post/141021/
                s.bind((str(multicast_ip), multicast_port))
            else:
                # Binding to a multicast address is not allowed on Windows, and it is not necessary there. Error is:
                #   OSError: [WinError 10049] The requested address is not valid in its context
                s.bind(("", multicast_port))
            try:
                # Note that using INADDR_ANY in IP_ADD_MEMBERSHIP doesn't actually mean "any",
                # it means "choose one automatically"; see https://tldp.org/HOWTO/Multicast-HOWTO-6.html
                # This is why we have to specify the interface explicitly here.
                s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
                             multicast_ip.packed + self._local.packed)
            except OSError as ex:
                s.close()
                if ex.errno in (errno.EADDRNOTAVAIL, errno.ENODEV):
                    raise InvalidMediaConfigurationError(
                        f"Could not register multicast group membership {multicast_ip} via {self._local} using {s} "
                        f"[{errno.errorcode[ex.errno]}]") from None
                raise  # pragma: no cover
        elif isinstance(data_specifier, ServiceDataSpecifier):
            local_port = service_data_specifier_to_udp_port(data_specifier)
            try:
                s.bind((str(self._local), local_port))
            except OSError as ex:
                s.close()
                if ex.errno in (errno.EADDRNOTAVAIL, errno.ENODEV):
                    raise InvalidMediaConfigurationError(
                        f"Could not bind input service socket to {self._local}:{local_port} "
                        f"[{errno.errorcode[ex.errno]}]") from None
                raise  # pragma: no cover
        else:
            assert False
        _logger.debug("%r: New input %r", self, s)
        return s
Esempio n. 2
0
def _construct_canalystii(
        parameters: _InterfaceParameters) -> can.ThreadSafeBus:
    if isinstance(parameters, _ClassicInterfaceParameters):
        return can.ThreadSafeBus(interface=parameters.interface_name,
                                 channel=parameters.channel_name,
                                 bitrate=parameters.bitrate)
    if isinstance(parameters, _FDInterfaceParameters):
        raise InvalidMediaConfigurationError(
            f"Interface does not support CAN FD: {parameters.interface_name}")
    assert False, "Internal error"
Esempio n. 3
0
    def make_output_socket(
            self, remote_node_id: typing.Optional[int],
            data_specifier: pyuavcan.transport.DataSpecifier) -> socket.socket:
        _logger.debug(
            "%r: Constructing new output socket for remote node %s and %s",
            self, remote_node_id, data_specifier)
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
                          socket.IPPROTO_UDP)
        s.setblocking(False)
        try:
            # Output sockets shall be bound, too, in order to ensure that outgoing packets have the correct
            # source IP address specified. This is particularly important for localhost; an unbound socket
            # there emits all packets from 127.0.0.1 which is certainly not what we need.
            s.bind((str(self._local), 0))  # Bind to an ephemeral port.
        except OSError as ex:
            s.close()
            if ex.errno == errno.EADDRNOTAVAIL:
                raise InvalidMediaConfigurationError(
                    f"Bad IP configuration: cannot bind output socket to {self._local} [{errno.errorcode[ex.errno]}]"
                ) from None
            raise  # pragma: no cover

        if isinstance(data_specifier, MessageDataSpecifier):
            if remote_node_id is not None:
                s.close()
                raise UnsupportedSessionConfigurationError(
                    "Unicast message transfers are not defined.")
            # Merely binding is not enough for multicast sockets. We also have to configure IP_MULTICAST_IF.
            # https://tldp.org/HOWTO/Multicast-HOWTO-6.html
            # https://stackoverflow.com/a/26988214/1007777
            s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF,
                         self._local.packed)
            s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL,
                         IPv4SocketFactory.MULTICAST_TTL)
            remote_ip = message_data_specifier_to_multicast_group(
                self._local, data_specifier)
            remote_port = SUBJECT_PORT
        elif isinstance(data_specifier, ServiceDataSpecifier):
            if remote_node_id is None:
                s.close()
                raise UnsupportedSessionConfigurationError(
                    "Broadcast service transfers are not defined.")
            remote_ip = node_id_to_unicast_ip(self._local, remote_node_id)
            remote_port = service_data_specifier_to_udp_port(data_specifier)
        else:
            assert False

        s.connect((str(remote_ip), remote_port))
        _logger.debug("%r: New output %r connected to remote node %r", self, s,
                      remote_node_id)
        return s
Esempio n. 4
0
    def __init__(
        self,
        iface_name: str,
        bitrate: typing.Union[int, typing.Tuple[int, int]],
        mtu: typing.Optional[int] = None,
        *,
        loop: typing.Optional[asyncio.AbstractEventLoop] = None,
    ) -> None:
        """
        :param iface_name: Interface name consisting of Python-CAN interface module name and its channel,
            separated with a colon. Supported interfaces are documented below.
            The semantics of the channel name are described in the documentation for Python-CAN.

            - Interface ``socketcan`` is implemented by :class:`can.interfaces.socketcan.SocketcanBus`.
              The bit rate values are only used to select Classic/FD mode.
              It is not possible to configure the actual CAN bit rate using this API.
              Example: ``socketcan:vcan0``

            - Interface ``kvaser`` is implemented by :class:`can.interfaces.kvaser.canlib.KvaserBus`.
              Example: ``kvaser:0``

            - Interface ``slcan`` is implemented by :class:`can.interfaces.slcan.slcanBus`.
              Only Classic CAN is supported.
              The serial port settings are fixed at 115200-8N1.
              Example: ``slcan:COM12``

            - Interface ``pcan`` is implemented by :class:`can.interfaces.pcan.PcanBus`.
              Ensure that `PCAN-Basic <https://www.peak-system.com/PCAN-Basic.239.0.html>`_ is installed.
              Example: ``pcan:PCAN_USBBUS1``

            - Interface ``virtual`` is described in https://python-can.readthedocs.io/en/master/interfaces/virtual.html.
              The channel name should be empty.
              Example: ``virtual:``

        :param bitrate: Bit rate value in bauds; either a single integer or a tuple:

            - A single integer selects Classic CAN.
            - A tuple of two selects CAN FD, where the first integer defines the arbitration (nominal) bit rate
              and the second one defines the data phase bit rate.
            - If MTU (see below) is given and is greater than 8 bytes, CAN FD is used regardless of the above.

        :param mtu: The maximum CAN data field size in bytes.
            If provided, this value must belong to :attr:`Media.VALID_MTU_SET`.
            If not provided, the default is determined as follows:

            - If `bitrate` is a single integer: classic CAN is assumed, MTU defaults to 8 bytes.
            - If `bitrate` is two integers: CAN FD is assumed, MTU defaults to 64 bytes.

        :param loop: The event loop to use. Defaults to :func:`asyncio.get_event_loop`.

        :raises: :class:`InvalidMediaConfigurationError` if the specified media instance
            could not be constructed, the interface name is unknown,
            or if the underlying library raised a :class:`can.CanError`.

        Use virtual bus with various bit rate and FD configurations:

        >>> media = PythonCANMedia('virtual:', 500_000)
        >>> media.is_fd, media.mtu
        (False, 8)
        >>> media = PythonCANMedia('virtual:', (500_000, 2_000_000))
        >>> media.is_fd, media.mtu
        (True, 64)
        >>> media = PythonCANMedia('virtual:', 1_000_000, 16)
        >>> media.is_fd, media.mtu
        (True, 16)

        Use PCAN-USB channel 1 in FD mode with nominal bitrate 500 kbit/s, data bitrate 2 Mbit/s, MTU 64 bytes::

            PythonCANMedia('pcan:PCAN_USBBUS1', (500_000, 2_000_000))

        Use Kvaser channel 0 in classic mode with bitrate 500k::

            PythonCANMedia('kvaser:0', 500_000)
        """
        self._conn_name = str(iface_name).split(":")
        if len(self._conn_name) != 2:
            raise InvalidMediaConfigurationError(
                f"Interface name {iface_name!r} does not match the format 'interface:channel'"
            )

        single_bitrate = isinstance(bitrate, (int, float))
        bitrate = (int(bitrate), int(bitrate)) if single_bitrate else (int(
            bitrate[0]), int(bitrate[1]))  # type: ignore

        self._mtu = int(mtu) if mtu is not None else (
            min(self.VALID_MTU_SET) if single_bitrate else 64)
        if self._mtu not in self.VALID_MTU_SET:
            raise InvalidMediaConfigurationError(f"Wrong MTU value: {mtu}")

        self._is_fd = self._mtu > min(self.VALID_MTU_SET) or not single_bitrate

        self._loop = loop if loop is not None else asyncio.get_event_loop()
        self._closed = False
        self._maybe_thread: typing.Optional[threading.Thread] = None
        self._rx_handler: typing.Optional[Media.ReceivedFramesHandler] = None
        self._background_executor = concurrent.futures.ThreadPoolExecutor(
            max_workers=1)

        params: typing.Union[_FDInterfaceParameters,
                             _ClassicInterfaceParameters]
        if self._is_fd:
            params = _FDInterfaceParameters(interface_name=self._conn_name[0],
                                            channel_name=self._conn_name[1],
                                            bitrate=bitrate)
        else:
            params = _ClassicInterfaceParameters(
                interface_name=self._conn_name[0],
                channel_name=self._conn_name[1],
                bitrate=bitrate[0])
        try:
            self._bus = _CONSTRUCTORS[self._conn_name[0]](params)
        except can.CanError as ex:
            raise InvalidMediaConfigurationError(
                f"Could not initialize PythonCAN: {ex}") from ex
        super().__init__()
Esempio n. 5
0
def _construct_any(parameters: _InterfaceParameters) -> can.ThreadSafeBus:
    raise InvalidMediaConfigurationError(
        f"Interface not supported yet: {parameters.interface_name}")