Skip to content

Commit e083978

Browse files
committed
Sync python/ and scripts/ from upstream
1 parent d294b2f commit e083978

File tree

9 files changed

+140
-141
lines changed

9 files changed

+140
-141
lines changed

python/__init__.py

Lines changed: 15 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -112,28 +112,22 @@ class Panda:
112112

113113
# from https://github.com/commaai/openpilot/blob/103b4df18cbc38f4129555ab8b15824d1a672bdf/cereal/log.capnp#L648
114114
HW_TYPE_UNKNOWN = b'\x00'
115-
HW_TYPE_WHITE = b'\x01'
116-
HW_TYPE_BLACK = b'\x03'
117115
HW_TYPE_RED_PANDA = b'\x07'
118116
HW_TYPE_TRES = b'\x09'
119117
HW_TYPE_CUATRO = b'\x0a'
118+
HW_TYPE_BODY = b'\xb1'
120119

121120
CAN_PACKET_VERSION = 4
122121
HEALTH_PACKET_VERSION = 17
123122
CAN_HEALTH_PACKET_VERSION = 5
124123
HEALTH_STRUCT = struct.Struct("<IIIIIIIIBBBBBHBBBHfBBHHHB")
125124
CAN_HEALTH_STRUCT = struct.Struct("<BIBBBBBBBBIIIIIIIHHBBBIIII")
126125

127-
H7_DEVICES = [HW_TYPE_RED_PANDA, HW_TYPE_TRES, HW_TYPE_CUATRO]
126+
H7_DEVICES = [HW_TYPE_RED_PANDA, HW_TYPE_TRES, HW_TYPE_CUATRO, HW_TYPE_BODY]
128127
SUPPORTED_DEVICES = H7_DEVICES
129128

130129
INTERNAL_DEVICES = (HW_TYPE_TRES, HW_TYPE_CUATRO)
131130

132-
MAX_FAN_RPMs = {
133-
HW_TYPE_TRES: 6600,
134-
HW_TYPE_CUATRO: 5000,
135-
}
136-
137131
HARNESS_STATUS_NC = 0
138132
HARNESS_STATUS_NORMAL = 1
139133
HARNESS_STATUS_FLIPPED = 2
@@ -147,11 +141,10 @@ def __init__(self, serial: str | None = None, claim: bool = True, disable_checks
147141
self._can_speed_kbps = can_speed_kbps
148142

149143
if cli and serial is None:
150-
self._connect_serial = self._cli_select_panda()
144+
self._connect_serial = self._cli_select_panda()
151145
else:
152-
self._connect_serial = serial
146+
self._connect_serial = serial
153147

154-
# connect and set mcu type
155148
self.connect(claim)
156149

157150
def _cli_select_panda(self):
@@ -208,14 +201,9 @@ def connect(self, claim=True, wait=False):
208201
self._serial = serial
209202
self._connect_serial = serial
210203
self._handle_open = True
211-
self._mcu_type = self.get_mcu_type()
212204
self.health_version, self.can_version, self.can_health_version = self.get_packets_versions()
213205
logger.debug("connected")
214206

215-
hw_type = self.get_type()
216-
if hw_type not in self.SUPPORTED_DEVICES:
217-
print("WARNING: Using deprecated HW")
218-
219207
# disable openpilot's heartbeat checks
220208
if self._disable_checks:
221209
self.set_heartbeat_disabled()
@@ -286,7 +274,7 @@ def usb_connect(cls, serial, claim=True, no_error=False):
286274
handle = device.open()
287275
if sys.platform not in ("win32", "cygwin", "msys", "darwin"):
288276
handle.setAutoDetachKernelDriver(True)
289-
if claim:
277+
if claim or sys.platform == "darwin":
290278
handle.claimInterface(0)
291279
# handle.setInterfaceAltSetting(0, 0) # Issue in USB stack
292280

@@ -342,6 +330,9 @@ def spi_list(cls):
342330
return []
343331

344332
def reset(self, enter_bootstub=False, enter_bootloader=False, reconnect=True):
333+
if enter_bootstub or enter_bootloader:
334+
assert (hw_type := self.get_type()) in self.SUPPORTED_DEVICES, f"Unknown HW: {hw_type}"
335+
345336
# no response is expected since it resets right away
346337
timeout = 5000 if isinstance(self._handle, PandaSpiHandle) else 15000
347338
try:
@@ -371,7 +362,7 @@ def reconnect(self):
371362
# wait up to 15 seconds
372363
for _ in range(15*10):
373364
try:
374-
self.connect(wait=True)
365+
self.connect(claim=False, wait=True)
375366
success = True
376367
break
377368
except Exception:
@@ -421,16 +412,14 @@ def flash_static(handle, code, mcu_type):
421412
pass
422413

423414
def flash(self, fn=None, code=None, reconnect=True):
415+
assert (hw_type := self.get_type()) in self.SUPPORTED_DEVICES, f"Unknown HW: {hw_type}"
416+
424417
if self.up_to_date(fn=fn):
425418
logger.info("flash: already up to date")
426419
return
427420

428-
hw_type = self.get_type()
429-
if hw_type not in self.SUPPORTED_DEVICES:
430-
raise RuntimeError(f"HW type {hw_type.hex()} is deprecated and can no longer be flashed.")
431-
432421
if not fn:
433-
fn = os.path.join(FW_PATH, self._mcu_type.config.app_fn)
422+
fn = os.path.join(FW_PATH, McuType.H7.config.app_fn)
434423
assert os.path.isfile(fn)
435424
logger.debug("flash: main version is %s", self.get_version())
436425
if not self.bootstub:
@@ -445,7 +434,7 @@ def flash(self, fn=None, code=None, reconnect=True):
445434
logger.debug("flash: bootstub version is %s", self.get_version())
446435

447436
# do flash
448-
Panda.flash_static(self._handle, code, mcu_type=self._mcu_type)
437+
Panda.flash_static(self._handle, code, mcu_type=McuType.H7)
449438

450439
# reconnect
451440
if reconnect:
@@ -496,7 +485,7 @@ def wait_for_panda(cls, serial: str | None, timeout: int) -> bool:
496485
def up_to_date(self, fn=None) -> bool:
497486
current = self.get_signature()
498487
if fn is None:
499-
fn = os.path.join(FW_PATH, self.get_mcu_type().config.app_fn)
488+
fn = os.path.join(FW_PATH, McuType.H7.config.app_fn)
500489
expected = Panda.get_signature_from_firmware(fn)
501490
return (current == expected)
502491

@@ -608,12 +597,6 @@ def get_packets_versions(self):
608597
else:
609598
return (0, 0, 0)
610599

611-
def get_mcu_type(self) -> McuType:
612-
hw_type = self.get_type()
613-
if hw_type in Panda.H7_DEVICES:
614-
return McuType.H7
615-
raise ValueError(f"unknown HW type: {hw_type}")
616-
617600
def is_internal(self):
618601
return self.get_type() in Panda.INTERNAL_DEVICES
619602

@@ -634,7 +617,7 @@ def get_usb_serial(self):
634617
return self._serial
635618

636619
def get_dfu_serial(self):
637-
return PandaDFU.st_serial_to_dfu_serial(self._serial, self._mcu_type)
620+
return PandaDFU.st_serial_to_dfu_serial(self._serial, McuType.H7)
638621

639622
def get_uid(self):
640623
"""
@@ -694,10 +677,6 @@ def set_uart_parity(self, uart, parity):
694677
def set_uart_callback(self, uart, install):
695678
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe3, uart, int(install), b'')
696679

697-
# LED stuff
698-
def set_led(self, led, state):
699-
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe9, led, state, b'')
700-
701680
# ******************* can *******************
702681

703682
# The panda will NAK CAN writes when there is CAN congestion.
@@ -742,15 +721,6 @@ def can_clear(self, bus):
742721
743722
"""
744723
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf1, bus, 0, b'')
745-
746-
def get_can_rx_slots(self):
747-
"""Returns the number of empty RX slots in the internal CAN ringbuffer.
748-
749-
Returns:
750-
int: number of empty RX slots.
751-
"""
752-
dat = self._handle.controlRead(Panda.REQUEST_IN, 0xc7, 0, 0, 2)
753-
return struct.unpack("H", dat)[0]
754724

755725
# ******************* serial *******************
756726

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

python/spi.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ class PandaSpiHandle(BaseHandle):
117117

118118
def __init__(self) -> None:
119119
self.dev = SpiDevice()
120+
self.no_retry = "NO_RETRY" in os.environ
120121

121122
# helpers
122123
def _calc_checksum(self, data: bytes) -> int:
@@ -193,6 +194,8 @@ def _transfer(self, endpoint: int, data, timeout: int, max_rx_len: int = 1000, e
193194
except PandaSpiException as e:
194195
exc = e
195196
logger.debug("SPI transfer failed, retrying", exc_info=True)
197+
if self.no_retry:
198+
break
196199

197200
# ensure slave is in a consistent state and ready for the next transfer
198201
# (e.g. slave TX buffer isn't stuck full)

scripts/can_health.py

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,10 @@
22
import time
33
import re
44
from panda import Panda
5-
from prettytable import PrettyTable
65

76
RED = '\033[91m'
87
GREEN = '\033[92m'
98

10-
def make_table(health1, health2, health3):
11-
table = PrettyTable()
12-
table.field_names = ["Key", "Bus 0", "Bus 1", "Bus 2"]
13-
for key1, value1 in health1.items():
14-
value2 = health2[key1]
15-
value3 = health3[key1]
16-
# Normalize float values
17-
if type(value1) is float:
18-
value1 = f"{value1:.4f}"
19-
value2 = f"{value2:.4f}"
20-
value3 = f"{value3:.4f}"
21-
table.add_row([key1, colorize_errors(value1), colorize_errors(value2), colorize_errors(value3)])
22-
23-
table.align["Key"] = "l"
24-
return table
25-
269
def colorize_errors(value):
2710
if isinstance(value, str):
2811
if re.search(r'(?i)No error', value):
@@ -37,9 +20,10 @@ def colorize_errors(value):
3720
while True:
3821
print(chr(27) + "[2J") # clear screen
3922
print("Connected to " + ("internal panda" if panda.is_internal() else "External panda") + f" id: {panda.get_serial()[0]}: {panda.get_version()}")
40-
health1 = panda.can_health(0)
41-
health2 = panda.can_health(1)
42-
health3 = panda.can_health(2)
43-
table = make_table(health1, health2, health3)
44-
print(table)
23+
for bus in range(3):
24+
print(f"\nBus {bus}:")
25+
health = panda.can_health(bus)
26+
for key, value in health.items():
27+
print(f"{key}: {colorize_errors(value)} ", end=" ")
28+
print()
4529
time.sleep(1)

scripts/can_printer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def can_printer():
3636

3737
if sec_since_boot() - lp > 0.1:
3838
dd = chr(27) + "[2J"
39-
dd += "Uptime: %5.2f - Count: %d\n" % (sec_since_boot() - start, len(all_msgs))
39+
dd += "%5.2f\n" % (sec_since_boot() - start)
4040
for (addr, bus), dat_log in sorted(all_msgs.items()):
4141
dd += "%d: %s(%6d): %s\n" % (bus, "%04X(%4d)" % (addr, addr), len(dat_log), binascii.hexlify(dat_log[-1]).decode())
4242
print(dd)

scripts/check_fw_size.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,12 @@ def check_space(file, mcu):
7878

7979

8080
if __name__ == "__main__":
81-
# red panda
82-
check_space("../board/obj/bootstub.panda_h7.elf", "H7")
83-
check_space("../board/obj/panda_h7.elf", "H7")
81+
# panda
82+
check_space("../board/obj/panda_h7/bootstub.elf", "H7")
83+
check_space("../board/obj/panda_h7/main.elf", "H7")
8484
# jungle v2
85-
check_space("../board/jungle/obj/bootstub.panda_jungle_h7.elf", "H7")
86-
check_space("../board/jungle/obj/panda_jungle_h7.elf", "H7")
85+
check_space("../board/obj/panda_jungle_h7/bootstub.elf", "H7")
86+
check_space("../board/obj/panda_jungle_h7/main.elf", "H7")
87+
# body
88+
check_space("../board/obj/body_h7/bootstub.elf", "H7")
89+
check_space("../board/obj/body_h7/main.elf", "H7")

scripts/fan/fan_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@
99
while True:
1010
p.set_fan_power(power)
1111
time.sleep(5)
12-
print("Power: ", power, "RPM:", str(p.get_fan_rpm()), "Expected:", int(6500 * power / 100))
12+
print("Power: ", power, "RPM:", str(p.get_fan_rpm()))
1313
power += 10
1414
power %= 110

0 commit comments

Comments
 (0)