Skip to content

Commit 9908056

Browse files
authored
Merge pull request #766 from hardbyte/felixdivo-patch-socketcan
Clean up socketcan
2 parents e0bb245 + 8f0a7e5 commit 9908056

File tree

4 files changed

+65
-55
lines changed

4 files changed

+65
-55
lines changed

can/interfaces/socketcan/socketcan.py

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
RestartableCyclicTaskABC,
3535
LimitedDurationCyclicSendTaskABC,
3636
)
37+
from can.typechecking import CanFilters
3738
from can.interfaces.socketcan.constants import * # CAN_RAW, CAN_*_FLAG
3839
from can.interfaces.socketcan.utils import pack_filters, find_available_interfaces
3940

@@ -593,20 +594,28 @@ def __init__(
593594
channel: str = "",
594595
receive_own_messages: bool = False,
595596
fd: bool = False,
597+
can_filters: Optional[CanFilters] = None,
596598
**kwargs,
597599
) -> None:
598-
"""
600+
"""Creates a new socketcan bus.
601+
602+
If setting some socket options fails, an error will be printed but no exception will be thrown.
603+
This includes enabling:
604+
- that own messages should be received,
605+
- CAN-FD frames and
606+
- error frames.
607+
599608
:param channel:
600-
The can interface name with which to create this bus. An example channel
601-
would be 'vcan0' or 'can0'.
609+
The can interface name with which to create this bus.
610+
An example channel would be 'vcan0' or 'can0'.
602611
An empty string '' will receive messages from all channels.
603612
In that case any sent messages must be explicitly addressed to a
604613
channel using :attr:`can.Message.channel`.
605614
:param receive_own_messages:
606615
If transmitted messages should also be received by this bus.
607616
:param fd:
608617
If CAN-FD frames should be supported.
609-
:param list can_filters:
618+
:param can_filters:
610619
See :meth:`can.BusABC.set_filters`.
611620
"""
612621
self.socket = create_socket()
@@ -622,53 +631,56 @@ def __init__(
622631
self.socket.setsockopt(
623632
SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, 1 if receive_own_messages else 0
624633
)
625-
except socket.error as e:
626-
log.error("Could not receive own messages (%s)", e)
634+
except socket.error as error:
635+
log.error("Could not receive own messages (%s)", error)
627636

637+
# enable CAN-FD frames
628638
if fd:
629-
# TODO handle errors
630-
self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_FD_FRAMES, 1)
639+
try:
640+
self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_FD_FRAMES, 1)
641+
except socket.error as error:
642+
log.error("Could not enable CAN-FD frames (%s)", error)
631643

632-
# Enable error frames
633-
self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_ERR_FILTER, 0x1FFFFFFF)
644+
# enable error frames
645+
try:
646+
self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_ERR_FILTER, 0x1FFFFFFF)
647+
except socket.error as error:
648+
log.error("Could not enable error frames (%s)", error)
634649

635650
bind_socket(self.socket, channel)
636651
kwargs.update({"receive_own_messages": receive_own_messages, "fd": fd})
637-
super().__init__(channel=channel, **kwargs)
652+
super().__init__(channel=channel, can_filters=can_filters, **kwargs)
638653

639654
def shutdown(self) -> None:
640655
"""Stops all active periodic tasks and closes the socket."""
641656
self.stop_all_periodic_tasks()
642-
for channel in self._bcm_sockets:
643-
log.debug("Closing bcm socket for channel {}".format(channel))
644-
bcm_socket = self._bcm_sockets[channel]
657+
for channel, bcm_socket in self._bcm_sockets.items():
658+
log.debug("Closing bcm socket for channel %s", channel)
645659
bcm_socket.close()
646660
log.debug("Closing raw can socket")
647661
self.socket.close()
648662

649663
def _recv_internal(
650664
self, timeout: Optional[float]
651665
) -> Tuple[Optional[Message], bool]:
652-
# get all sockets that are ready (can be a list with a single value
653-
# being self.socket or an empty list if self.socket is not ready)
654666
try:
655667
# get all sockets that are ready (can be a list with a single value
656668
# being self.socket or an empty list if self.socket is not ready)
657669
ready_receive_sockets, _, _ = select.select([self.socket], [], [], timeout)
658670
except socket.error as exc:
659671
# something bad happened (e.g. the interface went down)
660-
raise can.CanError("Failed to receive: %s" % exc)
672+
raise can.CanError(f"Failed to receive: {exc}")
661673

662-
if ready_receive_sockets: # not empty or True
674+
if ready_receive_sockets: # not empty
663675
get_channel = self.channel == ""
664676
msg = capture_message(self.socket, get_channel)
665677
if msg and not msg.channel and self.channel:
666678
# Default to our own channel
667679
msg.channel = self.channel
668680
return msg, self._is_filtered
669-
else:
670-
# socket wasn't readable or timeout occurred
671-
return None, self._is_filtered
681+
682+
# socket wasn't readable or timeout occurred
683+
return None, self._is_filtered
672684

673685
def send(self, msg: Message, timeout: Optional[float] = None) -> None:
674686
"""Transmit a message to the CAN bus.
@@ -777,13 +789,12 @@ def _get_bcm_socket(self, channel: str) -> socket.socket:
777789
def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None:
778790
try:
779791
self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_FILTER, pack_filters(filters))
780-
except socket.error as err:
792+
except socket.error as error:
781793
# fall back to "software filtering" (= not in kernel)
782794
self._is_filtered = False
783-
# TODO Is this serious enough to raise a CanError exception?
784795
log.error(
785796
"Setting filters failed; falling back to software filtering (not in kernel): %s",
786-
err,
797+
error,
787798
)
788799
else:
789800
self._is_filtered = True

can/interfaces/socketcan/utils.py

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,16 @@
22
Defines common socketcan functions.
33
"""
44

5-
from typing import cast, Iterable, Optional
6-
import can.typechecking as typechecking
7-
5+
import errno
86
import logging
97
import os
10-
import errno
8+
import re
119
import struct
1210
import subprocess
13-
import re
11+
from typing import cast, Iterable, Optional
1412

1513
from can.interfaces.socketcan.constants import CAN_EFF_FLAG
14+
import can.typechecking as typechecking
1615

1716
log = logging.getLogger(__name__)
1817

@@ -46,46 +45,38 @@ def find_available_interfaces() -> Iterable[str]:
4645
"""Returns the names of all open can/vcan interfaces using
4746
the ``ip link list`` command. If the lookup fails, an error
4847
is logged to the console and an empty list is returned.
49-
50-
:rtype: an iterable of :class:`str`
5148
"""
5249

5350
try:
54-
# it might be good to add "type vcan", but that might (?) exclude physical can devices
51+
# adding "type vcan" would exclude physical can devices
5552
command = ["ip", "-o", "link", "list", "up"]
5653
output = subprocess.check_output(command, universal_newlines=True)
5754

58-
except Exception as e: # subprocess.CalledProcessError was too specific
55+
except Exception as e: # subprocess.CalledProcessError is too specific
5956
log.error("failed to fetch opened can devices: %s", e)
6057
return []
6158

6259
else:
6360
# log.debug("find_available_interfaces(): output=\n%s", output)
6461
# output contains some lines like "1: vcan42: <NOARP,UP,LOWER_UP> ..."
6562
# extract the "vcan42" of each line
66-
interface_names = [line.split(": ", 3)[1] for line in output.splitlines()]
67-
log.debug("find_available_interfaces(): detected: %s", interface_names)
68-
return filter(_PATTERN_CAN_INTERFACE.match, interface_names)
63+
interfaces = [line.split(": ", 3)[1] for line in output.splitlines()]
64+
log.debug(
65+
"find_available_interfaces(): detected these interfaces (before filtering): %s",
66+
interfaces,
67+
)
68+
return filter(_PATTERN_CAN_INTERFACE.match, interfaces)
6969

7070

71-
def error_code_to_str(code: int) -> str:
71+
def error_code_to_str(code: Optional[int]) -> str:
7272
"""
7373
Converts a given error code (errno) to a useful and human readable string.
7474
75-
:param int code: a possibly invalid/unknown error code
76-
:rtype: str
75+
:param code: a possibly invalid/unknown error code
7776
:returns: a string explaining and containing the given error code, or a string
7877
explaining that the errorcode is unknown if that is the case
7978
"""
79+
name = errno.errorcode.get(code, "UNKNOWN") # type: ignore
80+
description = os.strerror(code) if code is not None else "NO DESCRIPTION AVAILABLE"
8081

81-
try:
82-
name = errno.errorcode[code]
83-
except KeyError:
84-
name = "UNKNOWN"
85-
86-
try:
87-
description = os.strerror(code)
88-
except ValueError:
89-
description = "no description available"
90-
91-
return "{} (errno {}): {}".format(name, code, description)
82+
return f"{name} (errno {code}): {description}"

can/io/blf.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -286,9 +286,17 @@ def _parse_data(self, data):
286286
)
287287
elif obj_type == CAN_FD_MESSAGE:
288288
members = unpack_can_fd_msg(data, pos)
289-
channel, flags, dlc, can_id, _, _, fd_flags, valid_bytes, can_data = (
290-
members
291-
)
289+
(
290+
channel,
291+
flags,
292+
dlc,
293+
can_id,
294+
_,
295+
_,
296+
fd_flags,
297+
valid_bytes,
298+
can_data,
299+
) = members
292300
yield Message(
293301
timestamp=timestamp,
294302
arbitration_id=can_id & 0x1FFFFFFF,

test/test_socketcan_helpers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from can.interfaces.socketcan.utils import find_available_interfaces, error_code_to_str
1111

12-
from .config import *
12+
from .config import IS_LINUX, TEST_INTERFACE_SOCKETCAN
1313

1414

1515
class TestSocketCanHelpers(unittest.TestCase):
@@ -21,7 +21,7 @@ def test_error_code_to_str(self):
2121
"""
2222

2323
# all possible & also some invalid error codes
24-
test_data = list(range(0, 256)) + [-1, 256, 5235, 346264]
24+
test_data = list(range(0, 256)) + [-1, 256, 5235, 346264, None]
2525

2626
for error_code in test_data:
2727
string = error_code_to_str(error_code)

0 commit comments

Comments
 (0)