Skip to content

Commit 25cb58b

Browse files
authored
SocketPanda improvements (#2297)
* SocketPanda improvements * implement timeouts
1 parent 515ac45 commit 25cb58b

File tree

1 file changed

+50
-23
lines changed

1 file changed

+50
-23
lines changed

python/socketpanda.py

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import socket
22
import struct
3+
import time
34

45
# /**
56
# * struct canfd_frame - CAN flexible data rate frame structure
@@ -23,6 +24,9 @@
2324
CAN_MAX_DLEN = 8
2425
CANFD_MAX_DLEN = 64
2526

27+
CAN_CONFIRM_FLAG = 0x800
28+
CAN_EFF_FLAG = 0x80000000
29+
2630
CANFD_BRS = 0x01 # bit rate switch (second bitrate for payload data)
2731
CANFD_FDF = 0x04 # mark CAN FD for dual use of struct canfd_frame
2832

@@ -32,11 +36,12 @@
3236

3337
import typing
3438
@typing.no_type_check # mypy struggles with macOS here...
35-
def create_socketcan(interface:str, recv_buffer_size:int, fd:bool) -> socket.socket:
39+
def create_socketcan(interface:str, recv_buffer_size:int) -> socket.socket:
3640
# settings mostly from https://github.com/linux-can/can-utils/blob/master/candump.c
3741
socketcan = socket.socket(socket.AF_CAN, socket.SOCK_RAW, socket.CAN_RAW)
38-
if fd:
39-
socketcan.setsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_FD_FRAMES, 1)
42+
socketcan.setblocking(False)
43+
socketcan.setsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_FD_FRAMES, 1)
44+
socketcan.setsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_RECV_OWN_MSGS, 1)
4045
socketcan.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, recv_buffer_size)
4146
# TODO: why is it always 2x the requested size?
4247
assert socketcan.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF) == recv_buffer_size * 2
@@ -47,50 +52,72 @@ def create_socketcan(interface:str, recv_buffer_size:int, fd:bool) -> socket.soc
4752

4853
# Panda class substitute for socketcan device (to support using the uds/iso-tp/xcp/ccp library)
4954
class SocketPanda():
50-
def __init__(self, interface:str="can0", bus:int=0, fd:bool=False, recv_buffer_size:int=212992) -> None:
55+
def __init__(self, interface:str="can0", recv_buffer_size:int=212992) -> None:
5156
self.interface = interface
52-
self.bus = bus
53-
self.fd = fd
54-
self.flags = CANFD_BRS | CANFD_FDF if fd else 0
55-
self.data_len = CANFD_MAX_DLEN if fd else CAN_MAX_DLEN
5657
self.recv_buffer_size = recv_buffer_size
57-
self.socket = create_socketcan(interface, recv_buffer_size, fd)
58+
self.socket = create_socketcan(interface, recv_buffer_size)
5859

5960
def __del__(self):
6061
self.socket.close()
6162

6263
def get_serial(self) -> tuple[int, int]:
63-
return (0, 0) # TODO: implemented in panda socketcan driver
64+
return (0, 0)
6465

6566
def get_version(self) -> int:
66-
return 0 # TODO: implemented in panda socketcan driver
67+
return 0
6768

6869
def can_clear(self, bus:int) -> None:
69-
# TODO: implemented in panda socketcan driver
7070
self.socket.close()
71-
self.socket = create_socketcan(self.interface, self.recv_buffer_size, self.fd)
71+
self.socket = create_socketcan(self.interface, self.recv_buffer_size)
7272

7373
def set_safety_mode(self, mode:int, param=0) -> None:
74-
pass # TODO: implemented in panda socketcan driver
74+
pass
7575

76-
def has_obd(self) -> bool:
77-
return False # TODO: implemented in panda socketcan driver
76+
def can_send_many(self, arr, *, fd=False, timeout=0) -> None:
77+
for msg in arr:
78+
self.can_send(*msg, fd=fd, timeout=timeout)
7879

79-
def can_send(self, addr, dat, bus=0, timeout=0) -> None:
80+
def can_send(self, addr, dat, bus, *, fd=False, timeout=0) -> None:
81+
# Even if the CANFD_FDF flag is not set, the data still must be 8 bytes for classic CAN frames.
82+
data_len = CANFD_MAX_DLEN if fd else CAN_MAX_DLEN
8083
msg_len = len(dat)
81-
msg_dat = dat.ljust(self.data_len, b'\x00')
82-
can_frame = struct.pack(CAN_HEADER_FMT, addr, msg_len, self.flags) + msg_dat
83-
self.socket.sendto(can_frame, (self.interface,))
84+
msg_dat = dat.ljust(data_len, b'\x00')
85+
86+
# Set extended ID flag
87+
if addr > 0x7ff:
88+
addr |= CAN_EFF_FLAG
89+
90+
# Set FD flags
91+
flags = CANFD_BRS | CANFD_FDF if fd else 0
92+
93+
can_frame = struct.pack(CAN_HEADER_FMT, addr, msg_len, flags) + msg_dat
94+
95+
# Try to send until timeout. sendto might block if the TX buffer is full.
96+
# TX buffer size can also be adjusted through `ip link set can0 txqueuelen <size>` if needed
97+
start_t = time.monotonic()
98+
while (time.monotonic() - start_t < (timeout / 1000)) or (timeout == 0):
99+
try:
100+
self.socket.sendto(can_frame, (self.interface,))
101+
break
102+
except (BlockingIOError, OSError):
103+
continue
104+
else:
105+
raise TimeoutError
106+
84107

85108
def can_recv(self) -> list[tuple[int, bytes, int]]:
86109
msgs = list()
87110
while True:
88111
try:
89-
dat, _ = self.socket.recvfrom(self.recv_buffer_size, socket.MSG_DONTWAIT)
90-
assert len(dat) == CAN_HEADER_LEN + self.data_len, f"ERROR: received {len(dat)} bytes"
112+
dat, _, msg_flags, _ = self.socket.recvmsg(self.recv_buffer_size)
113+
assert len(dat) >= CAN_HEADER_LEN, f"ERROR: received {len(dat)} bytes"
114+
91115
can_id, msg_len, _ = struct.unpack(CAN_HEADER_FMT, dat[:CAN_HEADER_LEN])
116+
assert len(dat) >= CAN_HEADER_LEN + msg_len, f"ERROR: received {len(dat)} bytes, expected at least {CAN_HEADER_LEN + msg_len} bytes"
117+
92118
msg_dat = dat[CAN_HEADER_LEN:CAN_HEADER_LEN+msg_len]
93-
msgs.append((can_id, msg_dat, self.bus))
119+
bus = 128 if (msg_flags & CAN_CONFIRM_FLAG) else 0
120+
msgs.append((can_id, msg_dat, bus))
94121
except BlockingIOError:
95122
break # buffered data exhausted
96123
return msgs

0 commit comments

Comments
 (0)