mirror of
https://github.com/dragonpilot/dragonpilot.git
synced 2026-08-21 16:23:50 +08:00
openpilot v0.9.7 release
date: 2024-06-11T01:36:39
master commit: f8cb04e4a8
This commit is contained in:
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
|
||||
from panda import Panda, PandaDFU
|
||||
from panda.tests.hitl.helpers import get_random_can_messages
|
||||
|
||||
|
||||
@contextmanager
|
||||
def print_time(desc):
|
||||
start = time.perf_counter()
|
||||
yield
|
||||
end = time.perf_counter()
|
||||
print(f"{end - start:.2f}s - {desc}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with print_time("Panda()"):
|
||||
p = Panda()
|
||||
|
||||
with print_time("PandaDFU.list()"):
|
||||
PandaDFU.list()
|
||||
|
||||
fxn = [
|
||||
'reset',
|
||||
'reconnect',
|
||||
'up_to_date',
|
||||
'health',
|
||||
#'flash',
|
||||
]
|
||||
for f in fxn:
|
||||
with print_time(f"Panda.{f}()"):
|
||||
getattr(p, f)()
|
||||
|
||||
p.set_can_loopback(True)
|
||||
|
||||
for n in range(6):
|
||||
msgs = get_random_can_messages(int(10**n))
|
||||
with print_time(f"Panda.can_send_many() - {len(msgs)} msgs"):
|
||||
p.can_send_many(msgs)
|
||||
|
||||
with print_time("Panda.can_recv()"):
|
||||
m = p.can_recv()
|
||||
Executable
+156
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Loopback test between black panda (+ harness and power) and white/grey panda
|
||||
# Tests all buses, including OBD CAN, which is on the same bus as CAN0 in this test.
|
||||
# To be sure, the test should be run with both harness orientations
|
||||
|
||||
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
import argparse
|
||||
from panda import Panda
|
||||
|
||||
def get_test_string():
|
||||
return b"test" + os.urandom(10)
|
||||
|
||||
counter = 0
|
||||
nonzero_bus_errors = 0
|
||||
zero_bus_errors = 0
|
||||
content_errors = 0
|
||||
|
||||
def run_test(sleep_duration):
|
||||
global counter
|
||||
|
||||
pandas = Panda.list()
|
||||
print(pandas)
|
||||
|
||||
# make sure two pandas are connected
|
||||
if len(pandas) != 2:
|
||||
raise Exception("Connect white/grey and black panda to run this test!")
|
||||
|
||||
# connect
|
||||
pandas[0] = Panda(pandas[0])
|
||||
pandas[1] = Panda(pandas[1])
|
||||
|
||||
black_panda = None
|
||||
other_panda = None
|
||||
|
||||
# find out which one is black
|
||||
if pandas[0].is_black() and not pandas[1].is_black():
|
||||
black_panda = pandas[0]
|
||||
other_panda = pandas[1]
|
||||
elif not pandas[0].is_black() and pandas[1].is_black():
|
||||
black_panda = pandas[1]
|
||||
other_panda = pandas[0]
|
||||
else:
|
||||
raise Exception("Connect white/grey and black panda to run this test!")
|
||||
|
||||
# disable safety modes
|
||||
black_panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
other_panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
|
||||
# test health packet
|
||||
print("black panda health", black_panda.health())
|
||||
print("other panda health", other_panda.health())
|
||||
|
||||
# test black -> other
|
||||
while True:
|
||||
test_buses(black_panda, other_panda, True, [(0, False, [0]), (1, False, [1]), (2, False, [2]), (1, True, [0])], sleep_duration)
|
||||
test_buses(black_panda, other_panda, False, [(0, False, [0]), (1, False, [1]), (2, False, [2]), (0, True, [0, 1])], sleep_duration)
|
||||
counter += 1
|
||||
print("Number of cycles:", counter, "Non-zero bus errors:", nonzero_bus_errors, "Zero bus errors:", zero_bus_errors, "Content errors:", content_errors)
|
||||
|
||||
# Toggle relay
|
||||
black_panda.set_safety_mode(Panda.SAFETY_SILENT)
|
||||
time.sleep(1)
|
||||
black_panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def test_buses(black_panda, other_panda, direction, test_array, sleep_duration):
|
||||
global nonzero_bus_errors, zero_bus_errors, content_errors
|
||||
|
||||
if direction:
|
||||
print("***************** TESTING (BLACK --> OTHER) *****************")
|
||||
else:
|
||||
print("***************** TESTING (OTHER --> BLACK) *****************")
|
||||
|
||||
for send_bus, obd, recv_buses in test_array:
|
||||
black_panda.send_heartbeat()
|
||||
other_panda.send_heartbeat()
|
||||
print("\ntest can: ", send_bus, " OBD: ", obd)
|
||||
|
||||
# set OBD on black panda
|
||||
black_panda.set_obd(True if obd else None)
|
||||
|
||||
# clear and flush
|
||||
if direction:
|
||||
black_panda.can_clear(send_bus)
|
||||
else:
|
||||
other_panda.can_clear(send_bus)
|
||||
|
||||
for recv_bus in recv_buses:
|
||||
if direction:
|
||||
other_panda.can_clear(recv_bus)
|
||||
else:
|
||||
black_panda.can_clear(recv_bus)
|
||||
|
||||
black_panda.can_recv()
|
||||
other_panda.can_recv()
|
||||
|
||||
# send the characters
|
||||
at = random.randint(1, 2000)
|
||||
st = get_test_string()[0:8]
|
||||
if direction:
|
||||
black_panda.can_send(at, st, send_bus)
|
||||
else:
|
||||
other_panda.can_send(at, st, send_bus)
|
||||
time.sleep(0.1)
|
||||
|
||||
# check for receive
|
||||
if direction:
|
||||
_ = black_panda.can_recv() # can echo
|
||||
cans_loop = other_panda.can_recv()
|
||||
else:
|
||||
_ = other_panda.can_recv() # can echo
|
||||
cans_loop = black_panda.can_recv()
|
||||
|
||||
loop_buses = []
|
||||
for loop in cans_loop:
|
||||
if (loop[0] != at) or (loop[2] != st):
|
||||
content_errors += 1
|
||||
|
||||
print(" Loop on bus", str(loop[3]))
|
||||
loop_buses.append(loop[3])
|
||||
if len(cans_loop) == 0:
|
||||
print(" No loop")
|
||||
assert not os.getenv("NOASSERT")
|
||||
|
||||
# test loop buses
|
||||
recv_buses.sort()
|
||||
loop_buses.sort()
|
||||
if(recv_buses != loop_buses):
|
||||
if len(loop_buses) == 0:
|
||||
zero_bus_errors += 1
|
||||
else:
|
||||
nonzero_bus_errors += 1
|
||||
assert not os.getenv("NOASSERT")
|
||||
else:
|
||||
print(" TEST PASSED")
|
||||
|
||||
time.sleep(sleep_duration)
|
||||
print("\n")
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-n", type=int, help="Number of test iterations to run")
|
||||
parser.add_argument("-sleep", type=int, help="Sleep time between tests", default=0)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.n is None:
|
||||
while True:
|
||||
run_test(sleep_duration=args.sleep)
|
||||
else:
|
||||
for _ in range(args.n):
|
||||
run_test(sleep_duration=args.sleep)
|
||||
Executable
+164
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Loopback test between black panda (+ harness and power) and white/grey panda
|
||||
# Tests all buses, including OBD CAN, which is on the same bus as CAN0 in this test.
|
||||
# To be sure, the test should be run with both harness orientations
|
||||
|
||||
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
import argparse
|
||||
|
||||
from panda import Panda
|
||||
|
||||
def get_test_string():
|
||||
return b"test" + os.urandom(10)
|
||||
|
||||
counter = 0
|
||||
nonzero_bus_errors = 0
|
||||
zero_bus_errors = 0
|
||||
content_errors = 0
|
||||
|
||||
def run_test(sleep_duration):
|
||||
global counter
|
||||
|
||||
pandas = Panda.list()
|
||||
print(pandas)
|
||||
|
||||
# make sure two pandas are connected
|
||||
if len(pandas) != 2:
|
||||
raise Exception("Connect white/grey and black panda to run this test!")
|
||||
|
||||
# connect
|
||||
pandas[0] = Panda(pandas[0])
|
||||
pandas[1] = Panda(pandas[1])
|
||||
|
||||
black_panda = None
|
||||
other_panda = None
|
||||
|
||||
# find out which one is black
|
||||
if pandas[0].is_black() and not pandas[1].is_black():
|
||||
black_panda = pandas[0]
|
||||
other_panda = pandas[1]
|
||||
elif not pandas[0].is_black() and pandas[1].is_black():
|
||||
black_panda = pandas[1]
|
||||
other_panda = pandas[0]
|
||||
else:
|
||||
raise Exception("Connect white/grey and black panda to run this test!")
|
||||
|
||||
# disable safety modes
|
||||
black_panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
other_panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
|
||||
# test health packet
|
||||
print("black panda health", black_panda.health())
|
||||
print("other panda health", other_panda.health())
|
||||
|
||||
# test black -> other
|
||||
start_time = time.time()
|
||||
temp_start_time = start_time
|
||||
while True:
|
||||
test_buses(black_panda, other_panda, True, [(0, False, [0]), (1, False, [1]), (2, False, [2]), (1, True, [0])], sleep_duration)
|
||||
test_buses(black_panda, other_panda, False, [(0, False, [0]), (1, False, [1]), (2, False, [2]), (0, True, [0, 1])], sleep_duration)
|
||||
counter += 1
|
||||
|
||||
runtime = time.time() - start_time
|
||||
print("Number of cycles:", counter, "Non-zero bus errors:", nonzero_bus_errors, "Zero bus errors:", zero_bus_errors,
|
||||
"Content errors:", content_errors, "Runtime: ", runtime)
|
||||
|
||||
if (time.time() - temp_start_time) > 3600 * 6:
|
||||
# Toggle relay
|
||||
black_panda.set_safety_mode(Panda.SAFETY_SILENT)
|
||||
time.sleep(1)
|
||||
black_panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
time.sleep(1)
|
||||
temp_start_time = time.time()
|
||||
|
||||
|
||||
def test_buses(black_panda, other_panda, direction, test_array, sleep_duration):
|
||||
global nonzero_bus_errors, zero_bus_errors, content_errors
|
||||
|
||||
if direction:
|
||||
print("***************** TESTING (BLACK --> OTHER) *****************")
|
||||
else:
|
||||
print("***************** TESTING (OTHER --> BLACK) *****************")
|
||||
|
||||
for send_bus, obd, recv_buses in test_array:
|
||||
black_panda.send_heartbeat()
|
||||
other_panda.send_heartbeat()
|
||||
print("\ntest can: ", send_bus, " OBD: ", obd)
|
||||
|
||||
# set OBD on black panda
|
||||
black_panda.set_obd(True if obd else None)
|
||||
|
||||
# clear and flush
|
||||
if direction:
|
||||
black_panda.can_clear(send_bus)
|
||||
else:
|
||||
other_panda.can_clear(send_bus)
|
||||
|
||||
for recv_bus in recv_buses:
|
||||
if direction:
|
||||
other_panda.can_clear(recv_bus)
|
||||
else:
|
||||
black_panda.can_clear(recv_bus)
|
||||
|
||||
black_panda.can_recv()
|
||||
other_panda.can_recv()
|
||||
|
||||
# send the characters
|
||||
at = random.randint(1, 2000)
|
||||
st = get_test_string()[0:8]
|
||||
if direction:
|
||||
black_panda.can_send(at, st, send_bus)
|
||||
else:
|
||||
other_panda.can_send(at, st, send_bus)
|
||||
time.sleep(0.1)
|
||||
|
||||
# check for receive
|
||||
if direction:
|
||||
_ = black_panda.can_recv() # cans echo
|
||||
cans_loop = other_panda.can_recv()
|
||||
else:
|
||||
_ = other_panda.can_recv() # cans echo
|
||||
cans_loop = black_panda.can_recv()
|
||||
|
||||
loop_buses = []
|
||||
for loop in cans_loop:
|
||||
if (loop[0] != at) or (loop[2] != st):
|
||||
content_errors += 1
|
||||
|
||||
print(" Loop on bus", str(loop[3]))
|
||||
loop_buses.append(loop[3])
|
||||
if len(cans_loop) == 0:
|
||||
print(" No loop")
|
||||
assert os.getenv("NOASSERT")
|
||||
|
||||
# test loop buses
|
||||
recv_buses.sort()
|
||||
loop_buses.sort()
|
||||
if(recv_buses != loop_buses):
|
||||
if len(loop_buses) == 0:
|
||||
zero_bus_errors += 1
|
||||
else:
|
||||
nonzero_bus_errors += 1
|
||||
assert os.getenv("NOASSERT")
|
||||
else:
|
||||
print(" TEST PASSED")
|
||||
|
||||
time.sleep(sleep_duration)
|
||||
print("\n")
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-n", type=int, help="Number of test iterations to run")
|
||||
parser.add_argument("-sleep", type=int, help="Sleep time between tests", default=0)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.n is None:
|
||||
while True:
|
||||
run_test(sleep_duration=args.sleep)
|
||||
else:
|
||||
for _ in range(args.n):
|
||||
run_test(sleep_duration=args.sleep)
|
||||
Executable
+135
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Relay test with loopback between black panda (+ harness and power) and white/grey panda
|
||||
# Tests the relay switching multiple times / second by looking at the buses on which loop occurs.
|
||||
|
||||
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
import argparse
|
||||
|
||||
from panda import Panda
|
||||
|
||||
def get_test_string():
|
||||
return b"test" + os.urandom(10)
|
||||
|
||||
counter = 0
|
||||
open_errors = 0
|
||||
closed_errors = 0
|
||||
content_errors = 0
|
||||
|
||||
def run_test(sleep_duration):
|
||||
global counter, open_errors, closed_errors
|
||||
|
||||
pandas = Panda.list()
|
||||
print(pandas)
|
||||
|
||||
# make sure two pandas are connected
|
||||
if len(pandas) != 2:
|
||||
raise Exception("Connect white/grey and black panda to run this test!")
|
||||
|
||||
# connect
|
||||
pandas[0] = Panda(pandas[0])
|
||||
pandas[1] = Panda(pandas[1])
|
||||
|
||||
# find out which one is black
|
||||
type0 = pandas[0].get_type()
|
||||
type1 = pandas[1].get_type()
|
||||
|
||||
black_panda = None
|
||||
other_panda = None
|
||||
|
||||
if type0 == "\x03" and type1 != "\x03":
|
||||
black_panda = pandas[0]
|
||||
other_panda = pandas[1]
|
||||
elif type0 != "\x03" and type1 == "\x03":
|
||||
black_panda = pandas[1]
|
||||
other_panda = pandas[0]
|
||||
else:
|
||||
raise Exception("Connect white/grey and black panda to run this test!")
|
||||
|
||||
# disable safety modes
|
||||
black_panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
other_panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
|
||||
# test health packet
|
||||
print("black panda health", black_panda.health())
|
||||
print("other panda health", other_panda.health())
|
||||
|
||||
# test black -> other
|
||||
while True:
|
||||
# Switch on relay
|
||||
black_panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
time.sleep(0.05)
|
||||
|
||||
if not test_buses(black_panda, other_panda, (0, False, [0])):
|
||||
open_errors += 1
|
||||
raise Exception("Open error")
|
||||
|
||||
# Switch off relay
|
||||
black_panda.set_safety_mode(Panda.SAFETY_SILENT)
|
||||
time.sleep(0.05)
|
||||
|
||||
if not test_buses(black_panda, other_panda, (0, False, [0, 2])):
|
||||
closed_errors += 1
|
||||
raise Exception("Close error")
|
||||
|
||||
counter += 1
|
||||
print("Number of cycles:", counter, "Open errors:", open_errors, "Closed errors:", closed_errors, "Content errors:", content_errors)
|
||||
|
||||
def test_buses(black_panda, other_panda, test_obj):
|
||||
global content_errors
|
||||
send_bus, obd, recv_buses = test_obj
|
||||
|
||||
black_panda.send_heartbeat()
|
||||
other_panda.send_heartbeat()
|
||||
|
||||
# Set OBD on send panda
|
||||
other_panda.set_obd(True if obd else None)
|
||||
|
||||
# clear and flush
|
||||
other_panda.can_clear(send_bus)
|
||||
|
||||
for recv_bus in recv_buses:
|
||||
black_panda.can_clear(recv_bus)
|
||||
|
||||
black_panda.can_recv()
|
||||
other_panda.can_recv()
|
||||
|
||||
# send the characters
|
||||
at = random.randint(1, 2000)
|
||||
st = get_test_string()[0:8]
|
||||
other_panda.can_send(at, st, send_bus)
|
||||
time.sleep(0.05)
|
||||
|
||||
# check for receive
|
||||
_ = other_panda.can_recv() # can echo
|
||||
cans_loop = black_panda.can_recv()
|
||||
|
||||
loop_buses = []
|
||||
for loop in cans_loop:
|
||||
if (loop[0] != at) or (loop[2] != st):
|
||||
content_errors += 1
|
||||
loop_buses.append(loop[3])
|
||||
|
||||
# test loop buses
|
||||
recv_buses.sort()
|
||||
loop_buses.sort()
|
||||
if(recv_buses != loop_buses):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-n", type=int, help="Number of test iterations to run")
|
||||
parser.add_argument("-sleep", type=int, help="Sleep time between tests", default=0)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.n is None:
|
||||
while True:
|
||||
run_test(sleep_duration=args.sleep)
|
||||
else:
|
||||
for _ in range(args.n):
|
||||
run_test(sleep_duration=args.sleep)
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import time
|
||||
import threading
|
||||
from typing import Any
|
||||
|
||||
from panda import Panda
|
||||
|
||||
JUNGLE = "JUNGLE" in os.environ
|
||||
if JUNGLE:
|
||||
from panda import PandaJungle
|
||||
|
||||
# The TX buffers on pandas is 0x100 in length.
|
||||
NUM_MESSAGES_PER_BUS = 10000
|
||||
|
||||
def flood_tx(panda):
|
||||
print('Sending!')
|
||||
msg = b"\xaa" * 4
|
||||
packet = [[0xaa, None, msg, 0], [0xaa, None, msg, 1], [0xaa, None, msg, 2]] * NUM_MESSAGES_PER_BUS
|
||||
panda.can_send_many(packet, timeout=10000)
|
||||
print(f"Done sending {3*NUM_MESSAGES_PER_BUS} messages!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
serials = Panda.list()
|
||||
if JUNGLE:
|
||||
sender = Panda()
|
||||
receiver = PandaJungle()
|
||||
else:
|
||||
if len(serials) != 2:
|
||||
raise Exception("Connect two pandas to perform this test!")
|
||||
sender = Panda(serials[0])
|
||||
receiver = Panda(serials[1]) # type: ignore
|
||||
receiver.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
|
||||
sender.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
|
||||
# Start transmisson
|
||||
threading.Thread(target=flood_tx, args=(sender,)).start()
|
||||
|
||||
# Receive as much as we can in a few second time period
|
||||
rx: list[Any] = []
|
||||
old_len = 0
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 3 or len(rx) > old_len:
|
||||
old_len = len(rx)
|
||||
print(old_len)
|
||||
rx.extend(receiver.can_recv())
|
||||
print(f"Received {len(rx)} messages")
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import time
|
||||
from collections import defaultdict
|
||||
import binascii
|
||||
|
||||
from panda import Panda
|
||||
|
||||
# fake
|
||||
def sec_since_boot():
|
||||
return time.time()
|
||||
|
||||
def can_printer():
|
||||
p = Panda()
|
||||
p.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
|
||||
start = sec_since_boot()
|
||||
lp = sec_since_boot()
|
||||
msgs = defaultdict(list)
|
||||
canbus = int(os.getenv("CAN", "0"))
|
||||
while True:
|
||||
can_recv = p.can_recv()
|
||||
for address, _, dat, src in can_recv:
|
||||
if src == canbus:
|
||||
msgs[address].append(dat)
|
||||
|
||||
if sec_since_boot() - lp > 0.1:
|
||||
dd = chr(27) + "[2J"
|
||||
dd += "%5.2f\n" % (sec_since_boot() - start)
|
||||
for k, v in sorted(zip(list(msgs.keys()), [binascii.hexlify(x[-1]) for x in list(msgs.values())], strict=True)):
|
||||
dd += "%s(%6d) %s\n" % ("%04X(%4d)" % (k, k), len(msgs[k]), v)
|
||||
print(dd)
|
||||
lp = sec_since_boot()
|
||||
|
||||
if __name__ == "__main__":
|
||||
can_printer()
|
||||
Executable
+152
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
from collections import defaultdict
|
||||
from panda import Panda, calculate_checksum, DLC_TO_LEN
|
||||
from panda import PandaJungle
|
||||
from panda.tests.hitl.helpers import time_many_sends
|
||||
|
||||
H7_HW_TYPES = [Panda.HW_TYPE_RED_PANDA, Panda.HW_TYPE_RED_PANDA_V2]
|
||||
JUNGLE_SERIAL = os.getenv("JUNGLE")
|
||||
H7_PANDAS_EXCLUDE = [] # type: ignore
|
||||
if os.getenv("H7_PANDAS_EXCLUDE"):
|
||||
H7_PANDAS_EXCLUDE = os.getenv("H7_PANDAS_EXCLUDE").strip().split(" ") # type: ignore
|
||||
|
||||
def panda_reset():
|
||||
panda_serials = []
|
||||
|
||||
panda_jungle = PandaJungle(JUNGLE_SERIAL)
|
||||
panda_jungle.set_can_silent(True)
|
||||
panda_jungle.set_panda_power(False)
|
||||
time.sleep(1)
|
||||
panda_jungle.set_panda_power(True)
|
||||
time.sleep(4)
|
||||
|
||||
for serial in Panda.list():
|
||||
if serial not in H7_PANDAS_EXCLUDE:
|
||||
with Panda(serial=serial) as p:
|
||||
if p.get_type() in H7_HW_TYPES:
|
||||
p.reset()
|
||||
panda_serials.append(serial)
|
||||
|
||||
print("test pandas", panda_serials)
|
||||
assert len(panda_serials) == 2, "Two H7 pandas required"
|
||||
|
||||
return panda_serials
|
||||
|
||||
def panda_init(serial, enable_canfd=False, enable_non_iso=False):
|
||||
p = Panda(serial=serial)
|
||||
p.set_power_save(False)
|
||||
for bus in range(3):
|
||||
p.set_can_speed_kbps(0, 500)
|
||||
if enable_canfd:
|
||||
p.set_can_data_speed_kbps(bus, 2000)
|
||||
if enable_non_iso:
|
||||
p.set_canfd_non_iso(bus, True)
|
||||
p.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
return p
|
||||
|
||||
def test_canfd_throughput(p, p_recv=None):
|
||||
two_pandas = p_recv is not None
|
||||
p.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
if two_pandas:
|
||||
p_recv.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
# enable output mode
|
||||
else:
|
||||
p.set_can_loopback(True)
|
||||
|
||||
tests = [
|
||||
[500, 1000, 2000], # speeds
|
||||
[93, 87, 78], # saturation thresholds
|
||||
]
|
||||
|
||||
for i in range(len(tests[0])):
|
||||
# set bus 0 data speed to speed
|
||||
p.set_can_data_speed_kbps(0, tests[0][i])
|
||||
if p_recv is not None:
|
||||
p_recv.set_can_data_speed_kbps(0, tests[0][i])
|
||||
time.sleep(0.05)
|
||||
|
||||
comp_kbps = time_many_sends(p, 0, p_recv=p_recv, msg_count=400, two_pandas=two_pandas, msg_len=64)
|
||||
|
||||
# bit count from https://en.wikipedia.org/wiki/CAN_bus
|
||||
saturation_pct = (comp_kbps / tests[0][i]) * 100.0
|
||||
assert saturation_pct > tests[1][i]
|
||||
assert saturation_pct < 100
|
||||
|
||||
def canfd_test(p_send, p_recv):
|
||||
for n in range(100):
|
||||
sent_msgs = defaultdict(set)
|
||||
to_send = []
|
||||
for _ in range(200):
|
||||
bus = random.randrange(3)
|
||||
for dlc in range(len(DLC_TO_LEN)):
|
||||
address = random.randrange(1, 1<<29)
|
||||
data = bytearray(random.getrandbits(8) for _ in range(DLC_TO_LEN[dlc]))
|
||||
if len(data) >= 2:
|
||||
data[0] = calculate_checksum(data[1:] + bytes(str(address), encoding="utf-8"))
|
||||
to_send.append([address, 0, data, bus])
|
||||
sent_msgs[bus].add((address, bytes(data)))
|
||||
|
||||
p_send.can_send_many(to_send, timeout=0)
|
||||
|
||||
start_time = time.monotonic()
|
||||
while (time.monotonic() - start_time < 1) and any(len(x) > 0 for x in sent_msgs.values()):
|
||||
incoming = p_recv.can_recv()
|
||||
for msg in incoming:
|
||||
address, _, data, bus = msg
|
||||
if len(data) >= 2:
|
||||
assert calculate_checksum(data[1:] + bytes(str(address), encoding="utf-8")) == data[0]
|
||||
k = (address, bytes(data))
|
||||
assert k in sent_msgs[bus], f"message {k} was never sent on bus {bus}"
|
||||
sent_msgs[bus].discard(k)
|
||||
|
||||
for bus in range(3):
|
||||
assert not len(sent_msgs[bus]), f"loop {n}: bus {bus} missing {len(sent_msgs[bus])} messages"
|
||||
|
||||
def setup_test(enable_non_iso=False):
|
||||
panda_serials = panda_reset()
|
||||
|
||||
p_send = panda_init(panda_serials[0], enable_canfd=False, enable_non_iso=enable_non_iso)
|
||||
p_recv = panda_init(panda_serials[1], enable_canfd=True, enable_non_iso=enable_non_iso)
|
||||
|
||||
# Check that sending panda CAN FD and BRS are turned off
|
||||
for bus in range(3):
|
||||
health = p_send.can_health(bus)
|
||||
assert not health["canfd_enabled"]
|
||||
assert not health["brs_enabled"]
|
||||
assert health["canfd_non_iso"] == enable_non_iso
|
||||
|
||||
# Receiving panda sends dummy CAN FD message that should enable CAN FD on sender side
|
||||
for bus in range(3):
|
||||
p_recv.can_send(0x200, b"dummymessage", bus)
|
||||
p_recv.can_recv()
|
||||
p_send.can_recv()
|
||||
|
||||
# Check if all tested buses on sending panda have swithed to CAN FD with BRS
|
||||
for bus in range(3):
|
||||
health = p_send.can_health(bus)
|
||||
assert health["canfd_enabled"]
|
||||
assert health["brs_enabled"]
|
||||
assert health["canfd_non_iso"] == enable_non_iso
|
||||
|
||||
return p_send, p_recv
|
||||
|
||||
def main():
|
||||
print("[TEST CAN-FD]")
|
||||
p_send, p_recv = setup_test()
|
||||
canfd_test(p_send, p_recv)
|
||||
|
||||
print("[TEST CAN-FD non-ISO]")
|
||||
p_send, p_recv = setup_test(enable_non_iso=True)
|
||||
canfd_test(p_send, p_recv)
|
||||
|
||||
print("[TEST CAN-FD THROUGHPUT]")
|
||||
panda_serials = panda_reset()
|
||||
p_send = panda_init(panda_serials[0], enable_canfd=True)
|
||||
p_recv = panda_init(panda_serials[1], enable_canfd=True)
|
||||
test_canfd_throughput(p_send, p_recv)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
def check_space(file, mcu):
|
||||
MCUS = {
|
||||
"H7": {
|
||||
".flash": 1024*1024, # FLASH
|
||||
".dtcmram": 128*1024, # DTCMRAM
|
||||
".itcmram": 64*1024, # ITCMRAM
|
||||
".axisram": 320*1024, # AXI SRAM
|
||||
".sram12": 32*1024, # SRAM1(16kb) + SRAM2(16kb)
|
||||
".sram4": 16*1024, # SRAM4
|
||||
".backup_sram": 4*1024, # SRAM4
|
||||
},
|
||||
"F4": {
|
||||
".flash": 1024*1024, # FLASH
|
||||
".dtcmram": 256*1024, # RAM
|
||||
".ram_d1": 64*1024, # RAM2
|
||||
},
|
||||
}
|
||||
IGNORE_LIST = [
|
||||
".ARM.attributes",
|
||||
".comment",
|
||||
".debug_line",
|
||||
".debug_info",
|
||||
".debug_abbrev",
|
||||
".debug_aranges",
|
||||
".debug_str",
|
||||
".debug_ranges",
|
||||
".debug_loc",
|
||||
".debug_frame",
|
||||
".debug_line_str",
|
||||
".debug_rnglists",
|
||||
".debug_loclists",
|
||||
]
|
||||
FLASH = [
|
||||
".isr_vector",
|
||||
".text",
|
||||
".rodata",
|
||||
".data"
|
||||
]
|
||||
RAM = [
|
||||
".data",
|
||||
".bss",
|
||||
"._user_heap_stack" # _user_heap_stack considered free?
|
||||
]
|
||||
|
||||
result = {}
|
||||
calcs = defaultdict(int)
|
||||
|
||||
output = str(subprocess.check_output(f"arm-none-eabi-size -x --format=sysv {file}", shell=True), 'utf-8')
|
||||
|
||||
for row in output.split('\n'):
|
||||
pop = False
|
||||
line = row.split()
|
||||
if len(line) == 3 and line[0].startswith('.'):
|
||||
if line[0] in IGNORE_LIST:
|
||||
continue
|
||||
result[line[0]] = [line[1], line[2]]
|
||||
if line[0] in FLASH:
|
||||
calcs[".flash"] += int(line[1], 16)
|
||||
pop = True
|
||||
if line[0] in RAM:
|
||||
calcs[".dtcmram"] += int(line[1], 16)
|
||||
pop = True
|
||||
if pop:
|
||||
result.pop(line[0])
|
||||
|
||||
if len(result):
|
||||
for line in result:
|
||||
calcs[line] += int(result[line][0], 16)
|
||||
|
||||
print(f"=======SUMMARY FOR {mcu} FILE {file}=======")
|
||||
for line in calcs:
|
||||
if line in MCUS[mcu]:
|
||||
used_percent = (100 - (MCUS[mcu][line] - calcs[line]) / MCUS[mcu][line] * 100)
|
||||
print(f"SECTION: {line} size: {MCUS[mcu][line]} USED: {calcs[line]}({used_percent:.2f}%) FREE: {MCUS[mcu][line] - calcs[line]}")
|
||||
else:
|
||||
print(line, calcs[line])
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# red panda
|
||||
check_space("../board/obj/bootstub.panda_h7.elf", "H7")
|
||||
check_space("../board/obj/panda_h7.elf", "H7")
|
||||
# black panda
|
||||
check_space("../board/obj/bootstub.panda.elf", "F4")
|
||||
check_space("../board/obj/panda.elf", "F4")
|
||||
# jungle v1
|
||||
check_space("../board/jungle/obj/bootstub.panda_jungle.elf", "F4")
|
||||
check_space("../board/jungle/obj/panda_jungle.elf", "F4")
|
||||
# jungle v2
|
||||
check_space("../board/jungle/obj/bootstub.panda_jungle_h7.elf", "H7")
|
||||
check_space("../board/jungle/obj/panda_jungle_h7.elf", "H7")
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
|
||||
OP_ROOT="$DIR/../../"
|
||||
PANDA_ROOT="$DIR/../"
|
||||
|
||||
if [ -z "$BUILD" ]; then
|
||||
docker pull docker.io/commaai/panda:latest
|
||||
else
|
||||
docker build --cache-from docker.io/commaai/panda:latest -t docker.io/commaai/panda:latest -f $PANDA_ROOT/Dockerfile $PANDA_ROOT
|
||||
fi
|
||||
|
||||
docker run \
|
||||
-it \
|
||||
--rm \
|
||||
--volume $OP_ROOT:$OP_ROOT \
|
||||
--workdir $PWD \
|
||||
--env PYTHONPATH=$OP_ROOT \
|
||||
docker.io/commaai/panda:latest \
|
||||
/bin/bash
|
||||
Executable
+62
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import select
|
||||
import codecs
|
||||
|
||||
from panda import Panda
|
||||
|
||||
setcolor = ["\033[1;32;40m", "\033[1;31;40m"]
|
||||
unsetcolor = "\033[00m"
|
||||
|
||||
port_number = int(os.getenv("PORT", "0"))
|
||||
claim = os.getenv("CLAIM") is not None
|
||||
no_color = os.getenv("NO_COLOR") is not None
|
||||
no_reconnect = os.getenv("NO_RECONNECT") is not None
|
||||
|
||||
if __name__ == "__main__":
|
||||
while True:
|
||||
try:
|
||||
serials = Panda.list()
|
||||
if os.getenv("SERIAL"):
|
||||
serials = [x for x in serials if x == os.getenv("SERIAL")]
|
||||
|
||||
pandas = [Panda(x, claim=claim) for x in serials]
|
||||
decoders = [codecs.getincrementaldecoder('utf-8')() for _ in pandas]
|
||||
|
||||
if not len(pandas):
|
||||
print("no pandas found")
|
||||
if no_reconnect:
|
||||
sys.exit(0)
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
if os.getenv("BAUD") is not None:
|
||||
for panda in pandas:
|
||||
panda.set_uart_baud(port_number, int(os.getenv("BAUD"))) # type: ignore
|
||||
|
||||
while True:
|
||||
for i, panda in enumerate(pandas):
|
||||
while True:
|
||||
ret = panda.serial_read(port_number)
|
||||
if len(ret) > 0:
|
||||
decoded = decoders[i].decode(ret)
|
||||
if no_color:
|
||||
sys.stdout.write(decoded)
|
||||
else:
|
||||
sys.stdout.write(setcolor[i] + decoded + unsetcolor)
|
||||
sys.stdout.flush()
|
||||
else:
|
||||
break
|
||||
if select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []):
|
||||
ln = sys.stdin.readline()
|
||||
if claim:
|
||||
panda.serial_write(port_number, ln)
|
||||
time.sleep(0.01)
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
except Exception:
|
||||
print("panda disconnected!")
|
||||
time.sleep(0.5)
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
import matplotlib.pyplot as plt # pylint: disable=import-error
|
||||
|
||||
HASHING_PRIME = 23
|
||||
REGISTER_MAP_SIZE = 0x3FF
|
||||
BYTES_PER_REG = 4
|
||||
|
||||
# From ST32F413 datasheet
|
||||
REGISTER_ADDRESS_REGIONS = [
|
||||
(0x40000000, 0x40007FFF),
|
||||
(0x40010000, 0x400107FF),
|
||||
(0x40011000, 0x400123FF),
|
||||
(0x40012C00, 0x40014BFF),
|
||||
(0x40015000, 0x400153FF),
|
||||
(0x40015800, 0x40015BFF),
|
||||
(0x40016000, 0x400167FF),
|
||||
(0x40020000, 0x40021FFF),
|
||||
(0x40023000, 0x400233FF),
|
||||
(0x40023800, 0x40023FFF),
|
||||
(0x40026000, 0x400267FF),
|
||||
(0x50000000, 0x5003FFFF),
|
||||
(0x50060000, 0x500603FF),
|
||||
(0x50060800, 0x50060BFF),
|
||||
(0x50060800, 0x50060BFF),
|
||||
(0xE0000000, 0xE00FFFFF)
|
||||
]
|
||||
|
||||
def _hash(reg_addr):
|
||||
return (((reg_addr >> 16) ^ ((((reg_addr + 1) & 0xFFFF) * HASHING_PRIME) & 0xFFFF)) & REGISTER_MAP_SIZE)
|
||||
|
||||
# Calculate hash for each address
|
||||
hashes = []
|
||||
double_hashes = []
|
||||
for (start_addr, stop_addr) in REGISTER_ADDRESS_REGIONS:
|
||||
for addr in range(start_addr, stop_addr + 1, BYTES_PER_REG):
|
||||
h = _hash(addr)
|
||||
hashes.append(h)
|
||||
double_hashes.append(_hash(h))
|
||||
|
||||
# Make histograms
|
||||
plt.subplot(2, 1, 1)
|
||||
plt.hist(hashes, bins=REGISTER_MAP_SIZE)
|
||||
plt.title("Number of collisions per _hash")
|
||||
plt.xlabel("Address")
|
||||
|
||||
plt.subplot(2, 1, 2)
|
||||
plt.hist(double_hashes, bins=REGISTER_MAP_SIZE)
|
||||
plt.title("Number of collisions per double _hash")
|
||||
plt.xlabel("Address")
|
||||
plt.show()
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
from panda import Panda
|
||||
|
||||
# This script is intended to be used in conjunction with the echo_loopback_test.py test script from panda jungle.
|
||||
# It sends a reversed response back for every message received containing b"test".
|
||||
if __name__ == "__main__":
|
||||
p = Panda()
|
||||
p.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
p.set_power_save(False)
|
||||
|
||||
while True:
|
||||
incoming = p.can_recv()
|
||||
for message in incoming:
|
||||
address, notused, data, bus = message
|
||||
if b'test' in data:
|
||||
p.can_send(address, data[::-1], bus)
|
||||
Executable
+232
@@ -0,0 +1,232 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Used to Reverse/Test ELM protocol auto detect and OBD message response without a car."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import struct
|
||||
import binascii
|
||||
import time
|
||||
import threading
|
||||
from collections import deque
|
||||
|
||||
from panda import Panda
|
||||
|
||||
def lin_checksum(dat):
|
||||
return sum(dat) % 0x100
|
||||
|
||||
class ELMCarSimulator():
|
||||
def __init__(self, sn, silent=False, can_kbaud=500,
|
||||
can=True, can11b=True, can29b=True,
|
||||
lin=True):
|
||||
self.__p = Panda(sn if sn else Panda.list()[0])
|
||||
self.__on = True
|
||||
self.__stop = False
|
||||
self.__silent = silent
|
||||
|
||||
self.__lin_timer = None
|
||||
self.__lin_active = False
|
||||
self.__lin_enable = lin
|
||||
self.__lin_monitor_thread = threading.Thread(target=self.__lin_monitor)
|
||||
|
||||
self.__can_multipart_data = None
|
||||
self.__can_kbaud = can_kbaud
|
||||
self.__can_extra_noise_msgs = deque()
|
||||
self.__can_enable = can
|
||||
self.__can11b = can11b
|
||||
self.__can29b = can29b
|
||||
self.__can_monitor_thread = threading.Thread(target=self.__can_monitor)
|
||||
|
||||
@property
|
||||
def panda(self):
|
||||
return self.__p
|
||||
|
||||
def stop(self):
|
||||
if self.__lin_timer:
|
||||
self.__lin_timer.cancel()
|
||||
self.__lin_timeout_handler()
|
||||
|
||||
self.__stop = True
|
||||
|
||||
def join(self):
|
||||
if self.__lin_monitor_thread.is_alive():
|
||||
self.__lin_monitor_thread.join()
|
||||
if self.__can_monitor_thread.is_alive():
|
||||
self.__can_monitor_thread.join()
|
||||
if self.__p:
|
||||
print("closing handle")
|
||||
self.__p.close()
|
||||
|
||||
def set_enable(self, on):
|
||||
self.__on = on
|
||||
|
||||
def start(self):
|
||||
self.panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
if self.__lin_enable:
|
||||
self.__lin_monitor_thread.start()
|
||||
if self.__can_enable:
|
||||
self.__can_monitor_thread.start()
|
||||
|
||||
#########################
|
||||
# CAN related functions #
|
||||
#########################
|
||||
|
||||
def __can_monitor(self):
|
||||
print("STARTING CAN THREAD")
|
||||
self.panda.set_can_speed_kbps(0, self.__can_kbaud)
|
||||
self.panda.can_recv() # Toss whatever was already there
|
||||
|
||||
while not self.__stop:
|
||||
for address, ts, data, src in self.panda.can_recv():
|
||||
if self.__on and src == 0 and len(data) == 8 and data[0] >= 2:
|
||||
if not self.__silent:
|
||||
print("Processing CAN message", src, hex(address), binascii.hexlify(data))
|
||||
self.__can_process_msg(data[1], data[2], address, ts, data, src)
|
||||
elif not self.__silent:
|
||||
print("Rejecting CAN message", src, hex(address), binascii.hexlify(data))
|
||||
|
||||
def can_mode_11b(self):
|
||||
self.__can11b = True
|
||||
self.__can29b = False
|
||||
|
||||
def can_mode_29b(self):
|
||||
self.__can11b = False
|
||||
self.__can29b = True
|
||||
|
||||
def can_mode_11b_29b(self):
|
||||
self.__can11b = True
|
||||
self.__can29b = True
|
||||
|
||||
def change_can_baud(self, kbaud):
|
||||
self.__can_kbaud = kbaud
|
||||
self.panda.set_can_speed_kbps(0, self.__can_kbaud)
|
||||
|
||||
def can_add_extra_noise(self, noise_msg, addr=None):
|
||||
self.__can_extra_noise_msgs.append((addr, noise_msg))
|
||||
|
||||
def _can_send(self, addr, msg):
|
||||
if not self.__silent:
|
||||
print(" CAN Reply (%x)" % addr, binascii.hexlify(msg))
|
||||
self.panda.can_send(addr, msg + b'\x00' * (8 - len(msg)), 0)
|
||||
if self.__can_extra_noise_msgs:
|
||||
noise = self.__can_extra_noise_msgs.popleft()
|
||||
self.panda.can_send(noise[0] if noise[0] is not None else addr,
|
||||
noise[1] + b'\x00' * (8 - len(noise[1])), 0)
|
||||
|
||||
def _can_addr_matches(self, addr):
|
||||
if self.__can11b and (addr == 0x7DF or (addr & 0x7F8) == 0x7E0):
|
||||
return True
|
||||
if self.__can29b and (addr == 0x18db33f1 or (addr & 0x1FFF00FF) == 0x18da00f1):
|
||||
return True
|
||||
return False
|
||||
|
||||
def __can_process_msg(self, mode, pid, address, ts, data, src):
|
||||
if not self.__silent:
|
||||
print("CAN MSG", binascii.hexlify(data[1:1 + data[0]]),
|
||||
"Addr:", hex(address), "Mode:", hex(mode)[2:].zfill(2),
|
||||
"PID:", hex(pid)[2:].zfill(2), "canLen:", len(data),
|
||||
binascii.hexlify(data))
|
||||
|
||||
if self._can_addr_matches(address) and len(data) == 8:
|
||||
outmsg = None
|
||||
if data[:3] == b'\x30\x00\x00' and len(self.__can_multipart_data):
|
||||
if not self.__silent:
|
||||
print("Request for more data")
|
||||
outaddr = 0x7E8 if address == 0x7DF or address == 0x7E0 else 0x18DAF110
|
||||
msgnum = 1
|
||||
while(self.__can_multipart_data):
|
||||
datalen = min(7, len(self.__can_multipart_data))
|
||||
msgpiece = struct.pack("B", 0x20 | msgnum) + self.__can_multipart_data[:datalen]
|
||||
self._can_send(outaddr, msgpiece)
|
||||
self.__can_multipart_data = self.__can_multipart_data[7:]
|
||||
msgnum = (msgnum + 1) % 0x10
|
||||
time.sleep(0.01)
|
||||
|
||||
else:
|
||||
outmsg = self._process_obd(mode, pid)
|
||||
|
||||
if outmsg:
|
||||
outaddr = 0x7E8 if address == 0x7DF or address == 0x7E0 else 0x18DAF110
|
||||
|
||||
if len(outmsg) <= 5:
|
||||
self._can_send(outaddr,
|
||||
struct.pack("BBB", len(outmsg) + 2, 0x40 | data[1], pid) + outmsg)
|
||||
else:
|
||||
first_msg_len = min(3, len(outmsg) % 7)
|
||||
payload_len = len(outmsg) + 3
|
||||
msgpiece = struct.pack("BBBBB", 0x10 | ((payload_len >> 8) & 0xF),
|
||||
payload_len & 0xFF,
|
||||
0x40 | data[1], pid, 1) + outmsg[:first_msg_len]
|
||||
self._can_send(outaddr, msgpiece)
|
||||
self.__can_multipart_data = outmsg[first_msg_len:]
|
||||
|
||||
#########################
|
||||
# General OBD functions #
|
||||
#########################
|
||||
|
||||
def _process_obd(self, mode, pid):
|
||||
if mode == 0x01: # Mode: Show current data
|
||||
if pid == 0x00: # List supported things
|
||||
return b"\xff\xff\xff\xfe" # b"\xBE\x1F\xB8\x10" #Bitfield, random features
|
||||
elif pid == 0x01: # Monitor Status since DTC cleared
|
||||
return b"\x00\x00\x00\x00" # Bitfield, random features
|
||||
elif pid == 0x04: # Calculated engine load
|
||||
return b"\x2f"
|
||||
elif pid == 0x05: # Engine coolant temperature
|
||||
return b"\x3c"
|
||||
elif pid == 0x0B: # Intake manifold absolute pressure
|
||||
return b"\x90"
|
||||
elif pid == 0x0C: # Engine RPM
|
||||
return b"\x1A\xF8"
|
||||
elif pid == 0x0D: # Vehicle Speed
|
||||
return b"\x53"
|
||||
elif pid == 0x10: # MAF air flow rate
|
||||
return b"\x01\xA0"
|
||||
elif pid == 0x11: # Throttle Position
|
||||
return b"\x90"
|
||||
elif pid == 0x33: # Absolute Barometric Pressure
|
||||
return b"\x90"
|
||||
elif mode == 0x09: # Mode: Request vehicle information
|
||||
if pid == 0x02: # Show VIN
|
||||
return b"1D4GP00R55B123456"
|
||||
if pid == 0xFC: # test long multi message. Ligned up for LIN responses
|
||||
return b''.join(struct.pack(">BBH", 0xAA, 0xAA, num + 1) for num in range(80))
|
||||
if pid == 0xFD: # test long multi message
|
||||
parts = (b'\xAA\xAA\xAA' + struct.pack(">I", num) for num in range(80))
|
||||
return b'\xAA\xAA\xAA' + b''.join(parts)
|
||||
if pid == 0xFE: # test very long multi message
|
||||
parts = (b'\xAA\xAA\xAA' + struct.pack(">I", num) for num in range(584))
|
||||
return b'\xAA\xAA\xAA' + b''.join(parts) + b'\xAA'
|
||||
if pid == 0xFF:
|
||||
return b'\xAA\x00\x00' + \
|
||||
b"".join((b'\xAA' * 5) + struct.pack(">H", num + 1) for num in range(584))
|
||||
#return b"\xAA"*100#(0xFFF-3)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
serial = os.getenv("SERIAL") if os.getenv("SERIAL") else None
|
||||
kbaud = int(os.getenv("CANKBAUD")) if os.getenv("CANKBAUD") else 500 # type: ignore
|
||||
bitwidth = int(os.getenv("CANBITWIDTH")) if os.getenv("CANBITWIDTH") else 0 # type: ignore
|
||||
canenable = bool(int(os.getenv("CANENABLE"))) if os.getenv("CANENABLE") else True # type: ignore
|
||||
linenable = bool(int(os.getenv("LINENABLE"))) if os.getenv("LINENABLE") else True # type: ignore
|
||||
sim = ELMCarSimulator(serial, can_kbaud=kbaud, can=canenable, lin=linenable)
|
||||
if(bitwidth == 0):
|
||||
sim.can_mode_11b_29b()
|
||||
if(bitwidth == 11):
|
||||
sim.can_mode_11b()
|
||||
if(bitwidth == 29):
|
||||
sim.can_mode_29b()
|
||||
|
||||
import signal
|
||||
|
||||
def signal_handler(signal, frame):
|
||||
print('\nShutting down simulator')
|
||||
sim.stop()
|
||||
sim.join()
|
||||
sys.exit(0)
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
sim.start()
|
||||
|
||||
signal.pause()
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import socket
|
||||
import threading
|
||||
import select
|
||||
|
||||
class Reader(threading.Thread):
|
||||
def __init__(self, s, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._s = s
|
||||
self.__stop = False
|
||||
|
||||
def stop(self):
|
||||
self.__stop = True
|
||||
|
||||
def run(self):
|
||||
while not self.__stop:
|
||||
s.recv(1000)
|
||||
|
||||
def read_or_fail(s):
|
||||
ready = select.select([s], [], [], 4)
|
||||
assert ready[0], "Socket did not receive data within the timeout duration."
|
||||
return s.recv(1000)
|
||||
|
||||
def send_msg(s, msg):
|
||||
s.send(msg)
|
||||
res = b''
|
||||
while not res.endswith(">"):
|
||||
res += read_or_fail(s)
|
||||
return res
|
||||
|
||||
if __name__ == "__main__":
|
||||
s = socket.create_connection(("192.168.0.10", 35000))
|
||||
send_msg(s, b"ATZ\r")
|
||||
send_msg(s, b"ATL1\r")
|
||||
print(send_msg(s, b"ATE0\r"))
|
||||
print(send_msg(s, b"ATS0\r"))
|
||||
print(send_msg(s, b"ATSP6\r"))
|
||||
|
||||
print("\nLOOP\n")
|
||||
|
||||
while True:
|
||||
print(send_msg(s, b"0100\r"))
|
||||
print(send_msg(s, b"010d\r"))
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
import time
|
||||
|
||||
from panda import Panda
|
||||
|
||||
if __name__ == "__main__":
|
||||
p = Panda()
|
||||
power = 0
|
||||
while True:
|
||||
p.set_fan_power(power)
|
||||
time.sleep(5)
|
||||
print("Power: ", power, "RPM:", str(p.get_fan_rpm()), "Expected:", int(6500 * power / 100))
|
||||
power += 10
|
||||
power %= 110
|
||||
Executable
+88
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import time
|
||||
import threading
|
||||
|
||||
from panda import Panda
|
||||
|
||||
def drain_serial(p):
|
||||
ret = []
|
||||
while True:
|
||||
d = p.serial_read(0)
|
||||
if len(d) == 0:
|
||||
break
|
||||
ret.append(d)
|
||||
return ret
|
||||
|
||||
|
||||
fan_cmd = 0.
|
||||
|
||||
def logger(event):
|
||||
# requires a build with DEBUG_FAN
|
||||
with Panda(claim=False) as p, open('/tmp/fan_log', 'w') as f:
|
||||
power = None
|
||||
target_rpm = None
|
||||
stall_count = None
|
||||
rpm_fast = None
|
||||
t = time.monotonic()
|
||||
|
||||
drain_serial(p)
|
||||
while not event.is_set():
|
||||
p.set_fan_power(fan_cmd)
|
||||
|
||||
for l in drain_serial(p)[::-1]:
|
||||
ns = l.decode('utf8').strip().split(' ')
|
||||
if len(ns) == 4:
|
||||
target_rpm, rpm_fast, power, stall_count = (int(n, 16) for n in ns)
|
||||
break
|
||||
|
||||
dat = {
|
||||
't': time.monotonic() - t,
|
||||
'cmd_power': fan_cmd,
|
||||
'pwm_power': power,
|
||||
'target_rpm': target_rpm,
|
||||
'rpm_fast': rpm_fast,
|
||||
'rpm': p.get_fan_rpm(),
|
||||
'stall_counter': stall_count,
|
||||
'total_stall_count': p.health()['fan_stall_count'],
|
||||
}
|
||||
f.write(json.dumps(dat) + '\n')
|
||||
time.sleep(1/16.)
|
||||
p.set_fan_power(0)
|
||||
|
||||
def get_overshoot_rpm(p, power):
|
||||
global fan_cmd
|
||||
|
||||
# make sure the fan is stopped completely
|
||||
fan_cmd = 0.
|
||||
while p.get_fan_rpm() > 100:
|
||||
time.sleep(0.1)
|
||||
time.sleep(3)
|
||||
|
||||
# set it to 30% power to mimic going onroad
|
||||
fan_cmd = power
|
||||
max_rpm = 0
|
||||
max_power = 0
|
||||
for _ in range(70):
|
||||
max_rpm = max(max_rpm, p.get_fan_rpm())
|
||||
max_power = max(max_power, p.health()['fan_power'])
|
||||
time.sleep(0.1)
|
||||
|
||||
# tolerate 10% overshoot
|
||||
expected_rpm = Panda.MAX_FAN_RPMs[bytes(p.get_type())] * power / 100
|
||||
overshoot = (max_rpm / expected_rpm) - 1
|
||||
|
||||
return overshoot, max_rpm, max_power
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
event = threading.Event()
|
||||
threading.Thread(target=logger, args=(event, )).start()
|
||||
|
||||
try:
|
||||
p = Panda()
|
||||
for power in range(10, 101, 10):
|
||||
overshoot, max_rpm, max_power = get_overshoot_rpm(p, power)
|
||||
print(f"Fan power {power}%: overshoot {overshoot:.2%}, Max RPM {max_rpm}, Max power {max_power}%")
|
||||
finally:
|
||||
event.set()
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
from panda import Panda
|
||||
|
||||
if __name__ == "__main__":
|
||||
for p in Panda.list():
|
||||
pp = Panda(p)
|
||||
print(f"{pp.get_serial()[0]}: {pp.get_version()}")
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
import time
|
||||
from panda import Panda
|
||||
|
||||
if __name__ == "__main__":
|
||||
i = 0
|
||||
pi = 0
|
||||
|
||||
panda = Panda()
|
||||
while True:
|
||||
st = time.monotonic()
|
||||
while time.monotonic() - st < 1:
|
||||
panda.health()
|
||||
i += 1
|
||||
print(i, panda.health(), "\n")
|
||||
print(f"Speed: {i - pi}Hz")
|
||||
pi = i
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import os
|
||||
import time
|
||||
import pytest
|
||||
|
||||
from panda import Panda, PandaDFU, McuType, BASEDIR
|
||||
|
||||
|
||||
def check_signature(p):
|
||||
assert not p.bootstub, "Flashed firmware not booting. Stuck in bootstub."
|
||||
assert p.up_to_date()
|
||||
|
||||
|
||||
def test_dfu(p):
|
||||
app_mcu_type = p.get_mcu_type()
|
||||
dfu_serial = p.get_dfu_serial()
|
||||
|
||||
p.reset(enter_bootstub=True)
|
||||
p.reset(enter_bootloader=True)
|
||||
assert Panda.wait_for_dfu(dfu_serial, timeout=19), "failed to enter DFU"
|
||||
|
||||
dfu = PandaDFU(dfu_serial)
|
||||
assert dfu.get_mcu_type() == app_mcu_type
|
||||
|
||||
assert dfu_serial in PandaDFU.list()
|
||||
|
||||
dfu._handle.clear_status()
|
||||
dfu.reset()
|
||||
p.reconnect()
|
||||
|
||||
# TODO: make more comprehensive bootstub tests and run on a few production ones + current
|
||||
# TODO: also test release-signed app
|
||||
@pytest.mark.timeout(30)
|
||||
def test_known_bootstub(p):
|
||||
"""
|
||||
Test that compiled app can work with known production bootstub
|
||||
"""
|
||||
known_bootstubs = {
|
||||
# covers the two cases listed in Panda.connect
|
||||
McuType.F4: [
|
||||
# case A - no bcdDevice or panda type, has to assume F4
|
||||
"bootstub_f4_first_dos_production.panda.bin",
|
||||
|
||||
# case B - just bcdDevice
|
||||
"bootstub_f4_only_bcd.panda.bin",
|
||||
],
|
||||
McuType.H7: ["bootstub.panda_h7.bin"],
|
||||
}
|
||||
|
||||
for kb in known_bootstubs[p.get_mcu_type()]:
|
||||
app_ids = (p.get_mcu_type(), p.get_usb_serial())
|
||||
assert None not in app_ids
|
||||
|
||||
p.reset(enter_bootstub=True)
|
||||
p.reset(enter_bootloader=True)
|
||||
|
||||
dfu_serial = p.get_dfu_serial()
|
||||
assert Panda.wait_for_dfu(dfu_serial, timeout=30)
|
||||
|
||||
dfu = PandaDFU(dfu_serial)
|
||||
with open(os.path.join(BASEDIR, "tests/hitl/known_bootstub", kb), "rb") as f:
|
||||
code = f.read()
|
||||
|
||||
dfu.program_bootstub(code)
|
||||
dfu.reset()
|
||||
|
||||
p.connect(claim=False, wait=True)
|
||||
|
||||
# check for MCU or serial mismatch
|
||||
with Panda(p._serial, claim=False) as np:
|
||||
bootstub_ids = (np.get_mcu_type(), np.get_usb_serial())
|
||||
assert app_ids == bootstub_ids
|
||||
|
||||
# ensure we can flash app and it jumps to app
|
||||
p.flash()
|
||||
check_signature(p)
|
||||
assert not p.bootstub
|
||||
|
||||
@pytest.mark.timeout(25)
|
||||
def test_recover(p):
|
||||
assert p.recover(timeout=30)
|
||||
check_signature(p)
|
||||
|
||||
@pytest.mark.timeout(25)
|
||||
def test_flash(p):
|
||||
# test flash from bootstub
|
||||
serial = p._serial
|
||||
assert serial is not None
|
||||
p.reset(enter_bootstub=True)
|
||||
p.close()
|
||||
time.sleep(2)
|
||||
|
||||
with Panda(serial) as np:
|
||||
assert np.bootstub
|
||||
assert np._serial == serial
|
||||
np.flash()
|
||||
|
||||
p.reconnect()
|
||||
p.reset()
|
||||
check_signature(p)
|
||||
|
||||
# test flash from app
|
||||
p.flash()
|
||||
check_signature(p)
|
||||
@@ -0,0 +1,65 @@
|
||||
import time
|
||||
import pytest
|
||||
|
||||
from panda import Panda
|
||||
|
||||
|
||||
@pytest.mark.skip_panda_types((Panda.HW_TYPE_DOS, ))
|
||||
def test_voltage(p):
|
||||
for _ in range(10):
|
||||
voltage = p.health()['voltage']
|
||||
assert ((voltage > 11000) and (voltage < 13000))
|
||||
time.sleep(0.1)
|
||||
|
||||
def test_hw_type(p):
|
||||
"""
|
||||
hw type should be same in bootstub as application
|
||||
"""
|
||||
|
||||
hw_type = p.get_type()
|
||||
mcu_type = p.get_mcu_type()
|
||||
assert mcu_type is not None
|
||||
|
||||
app_uid = p.get_uid()
|
||||
usb_serial = p.get_usb_serial()
|
||||
assert app_uid == usb_serial
|
||||
|
||||
p.reset(enter_bootstub=True, reconnect=True)
|
||||
p.close()
|
||||
time.sleep(3)
|
||||
with Panda(p.get_usb_serial()) as pp:
|
||||
assert pp.bootstub
|
||||
assert pp.get_type() == hw_type, "Bootstub and app hw type mismatch"
|
||||
assert pp.get_mcu_type() == mcu_type, "Bootstub and app MCU type mismatch"
|
||||
assert pp.get_uid() == app_uid
|
||||
|
||||
def test_heartbeat(p, panda_jungle):
|
||||
panda_jungle.set_ignition(True)
|
||||
# TODO: add more cases here once the tests aren't super slow
|
||||
p.set_safety_mode(mode=Panda.SAFETY_HYUNDAI, param=Panda.FLAG_HYUNDAI_LONG)
|
||||
p.send_heartbeat()
|
||||
assert p.health()['safety_mode'] == Panda.SAFETY_HYUNDAI
|
||||
assert p.health()['safety_param'] == Panda.FLAG_HYUNDAI_LONG
|
||||
|
||||
# shouldn't do anything once we're in a car safety mode
|
||||
p.set_heartbeat_disabled()
|
||||
|
||||
time.sleep(6.)
|
||||
|
||||
h = p.health()
|
||||
assert h['heartbeat_lost']
|
||||
assert h['safety_mode'] == Panda.SAFETY_SILENT
|
||||
assert h['safety_param'] == 0
|
||||
assert h['controls_allowed'] == 0
|
||||
|
||||
def test_microsecond_timer(p):
|
||||
start_time = p.get_microsecond_timer()
|
||||
time.sleep(1)
|
||||
end_time = p.get_microsecond_timer()
|
||||
|
||||
# account for uint32 overflow
|
||||
if end_time < start_time:
|
||||
end_time += 2**32
|
||||
|
||||
time_diff = (end_time - start_time) / 1e6
|
||||
assert 0.98 < time_diff < 1.02, f"Timer not running at the correct speed! (got {time_diff:.2f}s instead of 1.0s)"
|
||||
@@ -0,0 +1,86 @@
|
||||
import time
|
||||
from flaky import flaky
|
||||
|
||||
from panda import Panda
|
||||
from panda.tests.hitl.helpers import time_many_sends
|
||||
|
||||
def test_can_loopback(p):
|
||||
p.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
p.set_can_loopback(True)
|
||||
|
||||
for bus in (0, 1, 2):
|
||||
# set bus 0 speed to 5000
|
||||
p.set_can_speed_kbps(bus, 500)
|
||||
|
||||
# send a message on bus 0
|
||||
p.can_send(0x1aa, b"message", bus)
|
||||
|
||||
# confirm receive both on loopback and send receipt
|
||||
time.sleep(0.05)
|
||||
r = p.can_recv()
|
||||
sr = [x for x in r if x[3] == 0x80 | bus]
|
||||
lb = [x for x in r if x[3] == bus]
|
||||
assert len(sr) == 1
|
||||
assert len(lb) == 1
|
||||
|
||||
# confirm data is correct
|
||||
assert 0x1aa == sr[0][0] == lb[0][0]
|
||||
assert b"message" == sr[0][2] == lb[0][2]
|
||||
|
||||
def test_reliability(p):
|
||||
MSG_COUNT = 100
|
||||
|
||||
p.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
p.set_can_loopback(True)
|
||||
p.set_can_speed_kbps(0, 1000)
|
||||
|
||||
addrs = list(range(100, 100 + MSG_COUNT))
|
||||
ts = [(j, 0, b"\xaa" * 8, 0) for j in addrs]
|
||||
|
||||
for _ in range(100):
|
||||
st = time.monotonic()
|
||||
|
||||
p.can_send_many(ts)
|
||||
|
||||
r = []
|
||||
while len(r) < 200 and (time.monotonic() - st) < 0.5:
|
||||
r.extend(p.can_recv())
|
||||
|
||||
sent_echo = [x for x in r if x[3] == 0x80]
|
||||
loopback_resp = [x for x in r if x[3] == 0]
|
||||
|
||||
assert sorted([x[0] for x in loopback_resp]) == addrs
|
||||
assert sorted([x[0] for x in sent_echo]) == addrs
|
||||
assert len(r) == 200
|
||||
|
||||
# take sub 20ms
|
||||
et = (time.monotonic() - st) * 1000.0
|
||||
assert et < 20
|
||||
|
||||
@flaky(max_runs=6, min_passes=1)
|
||||
def test_throughput(p):
|
||||
# enable output mode
|
||||
p.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
|
||||
# enable CAN loopback mode
|
||||
p.set_can_loopback(True)
|
||||
|
||||
for speed in [10, 20, 50, 100, 125, 250, 500, 1000]:
|
||||
# set bus 0 speed to speed
|
||||
p.set_can_speed_kbps(0, speed)
|
||||
time.sleep(0.05)
|
||||
|
||||
comp_kbps = time_many_sends(p, 0)
|
||||
|
||||
# bit count from https://en.wikipedia.org/wiki/CAN_bus
|
||||
saturation_pct = (comp_kbps / speed) * 100.0
|
||||
assert saturation_pct > 80
|
||||
assert saturation_pct < 100
|
||||
|
||||
print("loopback 100 messages at speed %d, comp speed is %.2f, percent %.2f" % (speed, comp_kbps, saturation_pct))
|
||||
|
||||
# this will fail if you have hardware serial connected
|
||||
def test_serial_debug(p):
|
||||
_ = p.serial_read(Panda.SERIAL_DEBUG) # junk
|
||||
p.call_control_api(0x01)
|
||||
assert p.serial_read(Panda.SERIAL_DEBUG).startswith(b"NO HANDLER")
|
||||
@@ -0,0 +1,202 @@
|
||||
import os
|
||||
import time
|
||||
import pytest
|
||||
import random
|
||||
import threading
|
||||
from flaky import flaky
|
||||
from collections import defaultdict
|
||||
|
||||
from panda import Panda
|
||||
from panda.tests.hitl.conftest import PandaGroup
|
||||
from panda.tests.hitl.helpers import time_many_sends, get_random_can_messages, clear_can_buffers
|
||||
|
||||
@flaky(max_runs=3, min_passes=1)
|
||||
@pytest.mark.timeout(35)
|
||||
def test_send_recv(p, panda_jungle):
|
||||
def test(p_send, p_recv):
|
||||
for bus in (0, 1, 2):
|
||||
for speed in (10, 20, 50, 100, 125, 250, 500, 1000):
|
||||
clear_can_buffers(p_send, speed)
|
||||
clear_can_buffers(p_recv, speed)
|
||||
|
||||
comp_kbps = time_many_sends(p_send, bus, p_recv, two_pandas=True)
|
||||
|
||||
saturation_pct = (comp_kbps / speed) * 100.0
|
||||
assert 80 < saturation_pct < 100
|
||||
|
||||
print(f"two pandas bus {bus}, 100 messages at speed {speed:4d}, comp speed is {comp_kbps:7.2f}, {saturation_pct:6.2f}%")
|
||||
|
||||
# Run tests in both directions
|
||||
p.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
test(p, panda_jungle)
|
||||
test(panda_jungle, p)
|
||||
|
||||
|
||||
@flaky(max_runs=6, min_passes=1)
|
||||
@pytest.mark.timeout(30)
|
||||
def test_latency(p, panda_jungle):
|
||||
def test(p_send, p_recv):
|
||||
for bus in (0, 1, 2):
|
||||
for speed in (10, 20, 50, 100, 125, 250, 500, 1000):
|
||||
clear_can_buffers(p_send, speed)
|
||||
clear_can_buffers(p_recv, speed)
|
||||
|
||||
latencies = []
|
||||
comp_kbps_list = []
|
||||
saturation_pcts = []
|
||||
|
||||
num_messages = 100
|
||||
|
||||
for _ in range(num_messages):
|
||||
st = time.monotonic()
|
||||
p_send.can_send(0x1ab, b"message", bus)
|
||||
r = []
|
||||
while len(r) < 1 and (time.monotonic() - st) < 5:
|
||||
r = p_recv.can_recv()
|
||||
et = time.monotonic()
|
||||
r_echo = []
|
||||
while len(r_echo) < 1 and (time.monotonic() - st) < 10:
|
||||
r_echo = p_send.can_recv()
|
||||
|
||||
if len(r) == 0 or len(r_echo) == 0:
|
||||
print(f"r: {r}, r_echo: {r_echo}")
|
||||
|
||||
assert len(r) == 1
|
||||
assert len(r_echo) == 1
|
||||
|
||||
et = (et - st) * 1000.0
|
||||
comp_kbps = (1 + 11 + 1 + 1 + 1 + 4 + 8 * 8 + 15 + 1 + 1 + 1 + 7) / et
|
||||
latency = et - ((1 + 11 + 1 + 1 + 1 + 4 + 8 * 8 + 15 + 1 + 1 + 1 + 7) / speed)
|
||||
|
||||
assert latency < 5.0
|
||||
|
||||
saturation_pct = (comp_kbps / speed) * 100.0
|
||||
latencies.append(latency)
|
||||
comp_kbps_list.append(comp_kbps)
|
||||
saturation_pcts.append(saturation_pct)
|
||||
|
||||
average_latency = sum(latencies) / num_messages
|
||||
assert average_latency < 1.0
|
||||
average_comp_kbps = sum(comp_kbps_list) / num_messages
|
||||
average_saturation_pct = sum(saturation_pcts) / num_messages
|
||||
|
||||
print("two pandas bus {}, {} message average at speed {:4d}, latency is {:5.3f}ms, comp speed is {:7.2f}, percent {:6.2f}"
|
||||
.format(bus, num_messages, speed, average_latency, average_comp_kbps, average_saturation_pct))
|
||||
|
||||
# Run tests in both directions
|
||||
p.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
test(p, panda_jungle)
|
||||
test(panda_jungle, p)
|
||||
|
||||
|
||||
@pytest.mark.panda_expect_can_error
|
||||
@pytest.mark.test_panda_types(PandaGroup.GEN2)
|
||||
def test_gen2_loopback(p, panda_jungle):
|
||||
def test(p_send, p_recv, address=None):
|
||||
for bus in range(4):
|
||||
obd = False
|
||||
if bus == 3:
|
||||
obd = True
|
||||
bus = 1
|
||||
|
||||
# Clear buses
|
||||
clear_can_buffers(p_send)
|
||||
clear_can_buffers(p_recv)
|
||||
|
||||
# Send a random string
|
||||
addr = address if address else random.randint(1, 2000)
|
||||
string = b"test" + os.urandom(4)
|
||||
p_send.set_obd(obd)
|
||||
p_recv.set_obd(obd)
|
||||
time.sleep(0.2)
|
||||
p_send.can_send(addr, string, bus)
|
||||
time.sleep(0.2)
|
||||
|
||||
content = p_recv.can_recv()
|
||||
|
||||
# Check amount of messages
|
||||
assert len(content) == 1
|
||||
|
||||
# Check content
|
||||
assert content[0][0] == addr and content[0][2] == string
|
||||
|
||||
# Check bus
|
||||
assert content[0][3] == bus
|
||||
|
||||
print("Bus:", bus, "address:", addr, "OBD:", obd, "OK")
|
||||
|
||||
# Run tests in both directions
|
||||
p.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
test(p, panda_jungle)
|
||||
test(panda_jungle, p)
|
||||
|
||||
# Test extended frame address with ELM327 mode
|
||||
p.set_safety_mode(Panda.SAFETY_ELM327)
|
||||
test(p, panda_jungle, 0x18DB33F1)
|
||||
test(panda_jungle, p, 0x18DB33F1)
|
||||
|
||||
# TODO: why it's not being reset by fixtures reinit?
|
||||
p.set_obd(False)
|
||||
panda_jungle.set_obd(False)
|
||||
|
||||
def test_bulk_write(p, panda_jungle):
|
||||
# The TX buffers on pandas is 0x100 in length.
|
||||
NUM_MESSAGES_PER_BUS = 10000
|
||||
|
||||
def flood_tx(panda):
|
||||
print('Sending!')
|
||||
msg = b"\xaa" * 8
|
||||
packet = []
|
||||
# start with many messages on a single bus (higher contention for single TX ring buffer)
|
||||
packet += [[0xaa, None, msg, 0]] * NUM_MESSAGES_PER_BUS
|
||||
# end with many messages on multiple buses
|
||||
packet += [[0xaa, None, msg, 0], [0xaa, None, msg, 1], [0xaa, None, msg, 2]] * NUM_MESSAGES_PER_BUS
|
||||
|
||||
# Disable timeout
|
||||
panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
panda.can_send_many(packet, timeout=0)
|
||||
print(f"Done sending {4 * NUM_MESSAGES_PER_BUS} messages!", time.monotonic())
|
||||
print(panda.health())
|
||||
|
||||
# Start transmisson
|
||||
threading.Thread(target=flood_tx, args=(p,)).start()
|
||||
|
||||
# Receive as much as we can in a few second time period
|
||||
rx = []
|
||||
old_len = 0
|
||||
start_time = time.monotonic()
|
||||
while time.monotonic() - start_time < 5 or len(rx) > old_len:
|
||||
old_len = len(rx)
|
||||
rx.extend(panda_jungle.can_recv())
|
||||
print(f"Received {len(rx)} messages", time.monotonic())
|
||||
|
||||
# All messages should have been received
|
||||
if len(rx) != 4 * NUM_MESSAGES_PER_BUS:
|
||||
raise Exception("Did not receive all messages!")
|
||||
|
||||
def test_message_integrity(p):
|
||||
p.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
p.set_can_loopback(True)
|
||||
for i in range(250):
|
||||
sent_msgs = defaultdict(set)
|
||||
for _ in range(random.randrange(10)):
|
||||
to_send = get_random_can_messages(random.randrange(100))
|
||||
for m in to_send:
|
||||
sent_msgs[m[3]].add((m[0], m[2]))
|
||||
p.can_send_many(to_send, timeout=0)
|
||||
|
||||
start_time = time.monotonic()
|
||||
while time.monotonic() - start_time < 2 and any(len(sent_msgs[bus]) for bus in range(3)):
|
||||
recvd = p.can_recv()
|
||||
for msg in recvd:
|
||||
if msg[3] >= 128:
|
||||
k = (msg[0], bytes(msg[2]))
|
||||
bus = msg[3]-128
|
||||
assert k in sent_msgs[bus], f"message {k} was never sent on bus {bus}"
|
||||
sent_msgs[msg[3]-128].discard(k)
|
||||
|
||||
# if a set isn't empty, messages got dropped
|
||||
for bus in range(3):
|
||||
assert not len(sent_msgs[bus]), f"loop {i}: bus {bus} missing {len(sent_msgs[bus])} messages"
|
||||
|
||||
print("Got all messages intact")
|
||||
@@ -0,0 +1,103 @@
|
||||
import binascii
|
||||
import pytest
|
||||
import random
|
||||
from unittest.mock import patch
|
||||
|
||||
from panda import Panda, PandaDFU
|
||||
from panda.python.spi import SpiDevice, PandaProtocolMismatch, PandaSpiNackResponse
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.test_panda_types((Panda.HW_TYPE_TRES, ))
|
||||
]
|
||||
|
||||
@pytest.mark.skip("doesn't work, bootloader seems to ignore commands once it sees junk")
|
||||
def test_dfu_with_spam(p):
|
||||
dfu_serial = p.get_dfu_serial()
|
||||
|
||||
# enter DFU
|
||||
p.reset(enter_bootstub=True)
|
||||
p.reset(enter_bootloader=True)
|
||||
assert Panda.wait_for_dfu(dfu_serial, timeout=19), "failed to enter DFU"
|
||||
|
||||
# send junk
|
||||
d = SpiDevice()
|
||||
for _ in range(9):
|
||||
with d.acquire() as spi:
|
||||
dat = [random.randint(-1, 255) for _ in range(random.randint(1, 100))]
|
||||
spi.xfer(dat)
|
||||
|
||||
# should still show up
|
||||
assert dfu_serial in PandaDFU.list()
|
||||
|
||||
class TestSpi:
|
||||
def _ping(self, mocker, panda):
|
||||
# should work with no retries
|
||||
spy = mocker.spy(panda._handle, '_wait_for_ack')
|
||||
panda.health()
|
||||
assert spy.call_count == 2
|
||||
mocker.stop(spy)
|
||||
|
||||
def test_protocol_version_check(self, p):
|
||||
for bootstub in (False, True):
|
||||
p.reset(enter_bootstub=bootstub)
|
||||
with patch('panda.python.spi.PandaSpiHandle.PROTOCOL_VERSION', return_value="abc"):
|
||||
# list should still work with wrong version
|
||||
assert p._serial in Panda.list()
|
||||
|
||||
# connect but raise protocol error
|
||||
with pytest.raises(PandaProtocolMismatch):
|
||||
Panda(p._serial)
|
||||
|
||||
def test_protocol_version_data(self, p):
|
||||
for bootstub in (False, True):
|
||||
p.reset(enter_bootstub=bootstub)
|
||||
v = p._handle.get_protocol_version()
|
||||
|
||||
uid = binascii.hexlify(v[:12]).decode()
|
||||
assert uid == p.get_uid()
|
||||
|
||||
hwtype = v[12]
|
||||
assert hwtype == ord(p.get_type())
|
||||
|
||||
bstub = v[13]
|
||||
assert bstub == (0xEE if bootstub else 0xCC)
|
||||
|
||||
def test_all_comm_types(self, mocker, p):
|
||||
spy = mocker.spy(p._handle, '_wait_for_ack')
|
||||
|
||||
# controlRead + controlWrite
|
||||
p.health()
|
||||
p.can_clear(0)
|
||||
assert spy.call_count == 2*2
|
||||
|
||||
# bulkRead + bulkWrite
|
||||
p.can_recv()
|
||||
p.can_send(0x123, b"somedata", 0)
|
||||
assert spy.call_count == 2*4
|
||||
|
||||
def test_bad_header(self, mocker, p):
|
||||
with patch('panda.python.spi.SYNC', return_value=0):
|
||||
with pytest.raises(PandaSpiNackResponse):
|
||||
p._handle.controlRead(Panda.REQUEST_IN, 0xd2, 0, 0, p.HEALTH_STRUCT.size, timeout=50)
|
||||
self._ping(mocker, p)
|
||||
|
||||
def test_bad_checksum(self, mocker, p):
|
||||
cnt = p.health()['spi_checksum_error_count']
|
||||
with patch('panda.python.spi.PandaSpiHandle._calc_checksum', return_value=0):
|
||||
with pytest.raises(PandaSpiNackResponse):
|
||||
p._handle.controlRead(Panda.REQUEST_IN, 0xd2, 0, 0, p.HEALTH_STRUCT.size, timeout=50)
|
||||
self._ping(mocker, p)
|
||||
assert (p.health()['spi_checksum_error_count'] - cnt) > 0
|
||||
|
||||
def test_non_existent_endpoint(self, mocker, p):
|
||||
for _ in range(10):
|
||||
ep = random.randint(4, 20)
|
||||
with pytest.raises(PandaSpiNackResponse):
|
||||
p._handle.bulkRead(ep, random.randint(1, 1000), timeout=50)
|
||||
|
||||
self._ping(mocker, p)
|
||||
|
||||
with pytest.raises(PandaSpiNackResponse):
|
||||
p._handle.bulkWrite(ep, b"abc", timeout=50)
|
||||
|
||||
self._ping(mocker, p)
|
||||
@@ -0,0 +1,29 @@
|
||||
import time
|
||||
|
||||
from panda import Panda
|
||||
|
||||
|
||||
def test_safety_nooutput(p):
|
||||
p.set_safety_mode(Panda.SAFETY_SILENT)
|
||||
p.set_can_loopback(True)
|
||||
|
||||
# send a message on bus 0
|
||||
p.can_send(0x1aa, b"message", 0)
|
||||
|
||||
# confirm receive nothing
|
||||
time.sleep(0.05)
|
||||
r = p.can_recv()
|
||||
# bus 192 is messages blocked by TX safety hook on bus 0
|
||||
assert len([x for x in r if x[3] != 192]) == 0
|
||||
assert len([x for x in r if x[3] == 192]) == 1
|
||||
|
||||
|
||||
def test_canfd_safety_modes(p):
|
||||
# works on all pandas
|
||||
p.set_safety_mode(Panda.SAFETY_TOYOTA)
|
||||
assert p.health()['safety_mode'] == Panda.SAFETY_TOYOTA
|
||||
|
||||
# shouldn't be able to set a CAN-FD safety mode on non CAN-FD panda
|
||||
p.set_safety_mode(Panda.SAFETY_HYUNDAI_CANFD)
|
||||
expected_mode = Panda.SAFETY_HYUNDAI_CANFD if p.get_type() in Panda.H7_DEVICES else Panda.SAFETY_SILENT
|
||||
assert p.health()['safety_mode'] == expected_mode
|
||||
@@ -0,0 +1,68 @@
|
||||
import time
|
||||
import pytest
|
||||
|
||||
from panda import Panda
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.skip_panda_types(Panda.HW_TYPE_UNO),
|
||||
pytest.mark.test_panda_types(Panda.INTERNAL_DEVICES)
|
||||
]
|
||||
|
||||
@pytest.mark.timeout(2*60)
|
||||
def test_fan_controller(p):
|
||||
start_health = p.health()
|
||||
|
||||
for power in (30, 50, 80, 100):
|
||||
p.set_fan_power(0)
|
||||
while p.get_fan_rpm() > 0:
|
||||
time.sleep(0.1)
|
||||
|
||||
# wait until fan spins up (and recovers if needed),
|
||||
# then wait a bit more for the RPM to converge
|
||||
p.set_fan_power(power)
|
||||
for _ in range(20):
|
||||
time.sleep(1)
|
||||
if p.get_fan_rpm() > 1000:
|
||||
break
|
||||
time.sleep(5)
|
||||
|
||||
expected_rpm = Panda.MAX_FAN_RPMs[bytes(p.get_type())] * power / 100
|
||||
assert 0.9 * expected_rpm <= p.get_fan_rpm() <= 1.1 * expected_rpm
|
||||
|
||||
# Ensure the stall detection is tested on dos
|
||||
if p.get_type() == Panda.HW_TYPE_DOS:
|
||||
stalls = p.health()['fan_stall_count'] - start_health['fan_stall_count']
|
||||
assert stalls >= 2
|
||||
print("stall count", stalls)
|
||||
else:
|
||||
assert p.health()['fan_stall_count'] == 0
|
||||
|
||||
def test_fan_cooldown(p):
|
||||
# if the fan cooldown doesn't work, we get high frequency noise on the tach line
|
||||
# while the rotor spins down. this makes sure it never goes beyond the expected max RPM
|
||||
p.set_fan_power(100)
|
||||
time.sleep(3)
|
||||
p.set_fan_power(0)
|
||||
for _ in range(5):
|
||||
assert p.get_fan_rpm() <= 7000
|
||||
time.sleep(0.5)
|
||||
|
||||
def test_fan_overshoot(p):
|
||||
if p.get_type() == Panda.HW_TYPE_DOS:
|
||||
pytest.skip("panda's fan controller overshoots on the comma three fans that need stall recovery")
|
||||
|
||||
# make sure it's stopped completely
|
||||
p.set_fan_power(0)
|
||||
while p.get_fan_rpm() > 0:
|
||||
time.sleep(0.1)
|
||||
|
||||
# set it to 30% power to mimic going onroad
|
||||
p.set_fan_power(30)
|
||||
max_rpm = 0
|
||||
for _ in range(50):
|
||||
max_rpm = max(max_rpm, p.get_fan_rpm())
|
||||
time.sleep(0.1)
|
||||
|
||||
# tolerate 10% overshoot
|
||||
expected_rpm = Panda.MAX_FAN_RPMs[bytes(p.get_type())] * 30 / 100
|
||||
assert max_rpm <= 1.1 * expected_rpm, f"Fan overshoot: {(max_rpm / expected_rpm * 100) - 100:.1f}%"
|
||||
@@ -0,0 +1,13 @@
|
||||
import time
|
||||
|
||||
from panda import Panda
|
||||
|
||||
def test_boot_time(p):
|
||||
# boot time should be instant
|
||||
st = time.monotonic()
|
||||
p.reset(reconnect=False)
|
||||
assert Panda.wait_for_panda(p.get_usb_serial(), timeout=3.0)
|
||||
|
||||
# USB enumeration is slow, so SPI is faster
|
||||
assert time.monotonic() - st < (1.0 if p.spi else 5.0)
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import time
|
||||
import pytest
|
||||
import itertools
|
||||
|
||||
from panda import Panda
|
||||
from panda.tests.hitl.conftest import PandaGroup
|
||||
|
||||
# TODO: test relay
|
||||
|
||||
@pytest.mark.panda_expect_can_error
|
||||
@pytest.mark.test_panda_types(PandaGroup.GEN2)
|
||||
def test_harness_status(p, panda_jungle):
|
||||
# map from jungle orientations to panda orientations
|
||||
orientation_map = {
|
||||
Panda.HARNESS_STATUS_NC: Panda.HARNESS_STATUS_NC,
|
||||
}
|
||||
|
||||
# this shouldn't be parameterized since we don't want the panda to be reset
|
||||
# between the tests.
|
||||
for ignition, orientation in itertools.product([True, False], [Panda.HARNESS_STATUS_NC, Panda.HARNESS_STATUS_NORMAL, Panda.HARNESS_STATUS_FLIPPED]):
|
||||
print()
|
||||
p.set_safety_mode(Panda.SAFETY_ELM327)
|
||||
panda_jungle.set_harness_orientation(orientation)
|
||||
panda_jungle.set_ignition(ignition)
|
||||
|
||||
# wait for orientation detection
|
||||
time.sleep(0.25)
|
||||
|
||||
health = p.health()
|
||||
detected_orientation = health['car_harness_status']
|
||||
print(f"orientation set: {orientation} detected: {detected_orientation}")
|
||||
|
||||
if detected_orientation not in orientation_map:
|
||||
assert detected_orientation != Panda.HARNESS_STATUS_NC
|
||||
other = {Panda.HARNESS_STATUS_NORMAL: Panda.HARNESS_STATUS_FLIPPED, Panda.HARNESS_STATUS_FLIPPED: Panda.HARNESS_STATUS_NORMAL}
|
||||
orientation_map.update({
|
||||
orientation: detected_orientation,
|
||||
other[orientation]: other[detected_orientation],
|
||||
})
|
||||
|
||||
# Orientation
|
||||
assert orientation_map[detected_orientation] == orientation
|
||||
|
||||
# Line ignition
|
||||
assert health['ignition_line'] == (False if orientation == Panda.HARNESS_STATUS_NC else ignition)
|
||||
|
||||
# CAN traffic
|
||||
if orientation != Panda.HARNESS_STATUS_NC:
|
||||
for bus in range(3):
|
||||
panda_jungle.can_send(0x123, f"{bus}".encode(), bus)
|
||||
time.sleep(0.5)
|
||||
|
||||
msgs = p.can_recv()
|
||||
buses = {int(dat): bus for _, _, dat, bus in msgs if bus <= 3}
|
||||
print(msgs)
|
||||
|
||||
# jungle doesn't actually switch buses when switching orientation
|
||||
flipped = orientation == Panda.HARNESS_STATUS_FLIPPED
|
||||
assert buses[0] == (2 if flipped else 0)
|
||||
assert buses[2] == (0 if flipped else 2)
|
||||
|
||||
# SBU voltages
|
||||
supply_voltage_mV = 1800 if p.get_type() in [Panda.HW_TYPE_TRES, ] else 3300
|
||||
|
||||
if orientation == Panda.HARNESS_STATUS_NC:
|
||||
assert health['sbu1_voltage_mV'] > 0.9 * supply_voltage_mV
|
||||
assert health['sbu2_voltage_mV'] > 0.9 * supply_voltage_mV
|
||||
else:
|
||||
relay_line = 'sbu1_voltage_mV' if (detected_orientation == Panda.HARNESS_STATUS_FLIPPED) else 'sbu2_voltage_mV'
|
||||
ignition_line = 'sbu2_voltage_mV' if (detected_orientation == Panda.HARNESS_STATUS_FLIPPED) else 'sbu1_voltage_mV'
|
||||
|
||||
assert health[relay_line] < 0.1 * supply_voltage_mV
|
||||
assert health[ignition_line] > health[relay_line]
|
||||
if ignition:
|
||||
assert health[ignition_line] < 0.3 * supply_voltage_mV
|
||||
else:
|
||||
assert health[ignition_line] > 0.9 * supply_voltage_mV
|
||||
@@ -0,0 +1,222 @@
|
||||
import os
|
||||
import pytest
|
||||
import concurrent.futures
|
||||
|
||||
from panda import Panda, PandaDFU, PandaJungle
|
||||
from panda.tests.hitl.helpers import clear_can_buffers
|
||||
|
||||
# needed to get output when using xdist
|
||||
if "DEBUG" in os.environ:
|
||||
import sys
|
||||
sys.stdout = sys.stderr
|
||||
|
||||
SPEED_NORMAL = 500
|
||||
BUS_SPEEDS = [(0, SPEED_NORMAL), (1, SPEED_NORMAL), (2, SPEED_NORMAL)]
|
||||
|
||||
|
||||
JUNGLE_SERIAL = os.getenv("PANDAS_JUNGLE")
|
||||
NO_JUNGLE = os.environ.get("NO_JUNGLE", "0") == "1"
|
||||
PANDAS_EXCLUDE = os.getenv("PANDAS_EXCLUDE", "").strip().split(" ")
|
||||
HW_TYPES = os.environ.get("HW_TYPES", None)
|
||||
|
||||
PARALLEL = "PARALLEL" in os.environ
|
||||
NON_PARALLEL = "NON_PARALLEL" in os.environ
|
||||
if PARALLEL:
|
||||
NO_JUNGLE = True
|
||||
|
||||
class PandaGroup:
|
||||
H7 = (Panda.HW_TYPE_RED_PANDA, Panda.HW_TYPE_RED_PANDA_V2, Panda.HW_TYPE_TRES)
|
||||
GEN2 = (Panda.HW_TYPE_BLACK_PANDA, Panda.HW_TYPE_UNO, Panda.HW_TYPE_DOS) + H7
|
||||
TESTED = (Panda.HW_TYPE_WHITE_PANDA, Panda.HW_TYPE_BLACK_PANDA, Panda.HW_TYPE_RED_PANDA, Panda.HW_TYPE_RED_PANDA_V2, Panda.HW_TYPE_UNO)
|
||||
|
||||
if HW_TYPES is not None:
|
||||
PandaGroup.TESTED = [bytes([int(x), ]) for x in HW_TYPES.strip().split(",")] # type: ignore
|
||||
|
||||
|
||||
# Find all pandas connected
|
||||
_all_pandas = {}
|
||||
_panda_jungle = None
|
||||
def init_all_pandas():
|
||||
if not NO_JUNGLE:
|
||||
global _panda_jungle
|
||||
_panda_jungle = PandaJungle(JUNGLE_SERIAL)
|
||||
_panda_jungle.set_panda_power(True)
|
||||
|
||||
for serial in Panda.list():
|
||||
if serial not in PANDAS_EXCLUDE:
|
||||
with Panda(serial=serial, claim=False) as p:
|
||||
ptype = bytes(p.get_type())
|
||||
if ptype in PandaGroup.TESTED:
|
||||
_all_pandas[serial] = ptype
|
||||
|
||||
# ensure we have all tested panda types
|
||||
missing_types = set(PandaGroup.TESTED) - set(_all_pandas.values())
|
||||
assert len(missing_types) == 0, f"Missing panda types: {missing_types}"
|
||||
|
||||
print(f"{len(_all_pandas)} total pandas")
|
||||
init_all_pandas()
|
||||
_all_panda_serials = sorted(_all_pandas.keys())
|
||||
|
||||
|
||||
def init_jungle():
|
||||
if _panda_jungle is None:
|
||||
return
|
||||
clear_can_buffers(_panda_jungle)
|
||||
_panda_jungle.set_panda_power(True)
|
||||
_panda_jungle.set_can_loopback(False)
|
||||
_panda_jungle.set_obd(False)
|
||||
_panda_jungle.set_harness_orientation(PandaJungle.HARNESS_ORIENTATION_1)
|
||||
for bus, speed in BUS_SPEEDS:
|
||||
_panda_jungle.set_can_speed_kbps(bus, speed)
|
||||
|
||||
# ensure FW hasn't changed
|
||||
assert _panda_jungle.up_to_date()
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
config.addinivalue_line(
|
||||
"markers", "test_panda_types(name): whitelist a test for specific panda types"
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers", "skip_panda_types(name): blacklist panda types from a test"
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers", "panda_expect_can_error: mark test to ignore CAN health errors"
|
||||
)
|
||||
|
||||
@pytest.hookimpl(tryfirst=True)
|
||||
def pytest_collection_modifyitems(items):
|
||||
for item in items:
|
||||
if item.get_closest_marker('timeout') is None:
|
||||
item.add_marker(pytest.mark.timeout(60))
|
||||
|
||||
# xdist grouping by panda
|
||||
serial = item.name.split("serial=")[1].split(",")[0]
|
||||
assert len(serial) == 24
|
||||
item.add_marker(pytest.mark.xdist_group(serial))
|
||||
|
||||
needs_jungle = "panda_jungle" in item.fixturenames
|
||||
if PARALLEL and needs_jungle:
|
||||
item.add_marker(pytest.mark.skip(reason="no jungle tests in PARALLEL mode"))
|
||||
elif NON_PARALLEL and not needs_jungle:
|
||||
item.add_marker(pytest.mark.skip(reason="only running jungle tests"))
|
||||
|
||||
def pytest_make_parametrize_id(config, val, argname):
|
||||
if val in _all_pandas:
|
||||
# TODO: get nice string instead of int
|
||||
hw_type = _all_pandas[val][0]
|
||||
return f"serial={val}, hw_type={hw_type}"
|
||||
return None
|
||||
|
||||
|
||||
@pytest.fixture(name='panda_jungle', scope='function')
|
||||
def fixture_panda_jungle(request):
|
||||
init_jungle()
|
||||
return _panda_jungle
|
||||
|
||||
@pytest.fixture(name='p', scope='function')
|
||||
def func_fixture_panda(request, module_panda):
|
||||
p = module_panda
|
||||
|
||||
# Check if test is applicable to this panda
|
||||
mark = request.node.get_closest_marker('test_panda_types')
|
||||
if mark:
|
||||
assert len(mark.args) > 0, "Missing panda types argument in mark"
|
||||
test_types = mark.args[0]
|
||||
if _all_pandas[p.get_usb_serial()] not in test_types:
|
||||
pytest.skip(f"Not applicable, {test_types} pandas only")
|
||||
|
||||
mark = request.node.get_closest_marker('skip_panda_types')
|
||||
if mark:
|
||||
assert len(mark.args) > 0, "Missing panda types argument in mark"
|
||||
skip_types = mark.args[0]
|
||||
if _all_pandas[p.get_usb_serial()] in skip_types:
|
||||
pytest.skip(f"Not applicable to {skip_types}")
|
||||
|
||||
# this is 2+ seconds on USB pandas due to slow
|
||||
# enumeration on the host side
|
||||
p.reset()
|
||||
|
||||
# ensure FW hasn't changed
|
||||
assert p.up_to_date()
|
||||
|
||||
# Run test
|
||||
yield p
|
||||
|
||||
# Teardown
|
||||
|
||||
# reconnect
|
||||
if p.get_dfu_serial() in PandaDFU.list():
|
||||
PandaDFU(p.get_dfu_serial()).reset()
|
||||
p.reconnect()
|
||||
if not p.connected:
|
||||
p.reconnect()
|
||||
if p.bootstub:
|
||||
p.reset()
|
||||
|
||||
assert not p.bootstub
|
||||
|
||||
# TODO: would be nice to make these common checks in the teardown
|
||||
# show up as failed tests instead of "errors"
|
||||
|
||||
# Check for faults
|
||||
assert p.health()['faults'] == 0
|
||||
assert p.health()['fault_status'] == 0
|
||||
|
||||
# Check for SPI errors
|
||||
#assert p.health()['spi_checksum_error_count'] == 0
|
||||
|
||||
# Check health of each CAN core after test, normal to fail for test_gen2_loopback on OBD bus, so skipping
|
||||
mark = request.node.get_closest_marker('panda_expect_can_error')
|
||||
expect_can_error = mark is not None
|
||||
if not expect_can_error:
|
||||
for i in range(3):
|
||||
can_health = p.can_health(i)
|
||||
assert can_health['bus_off_cnt'] == 0
|
||||
assert can_health['receive_error_cnt'] < 127
|
||||
assert can_health['transmit_error_cnt'] < 255
|
||||
assert can_health['error_passive'] == 0
|
||||
assert can_health['error_warning'] == 0
|
||||
assert can_health['total_rx_lost_cnt'] == 0
|
||||
assert can_health['total_tx_lost_cnt'] == 0
|
||||
assert can_health['total_error_cnt'] == 0
|
||||
assert can_health['total_tx_checksum_error_cnt'] == 0
|
||||
|
||||
@pytest.fixture(name='module_panda', params=_all_panda_serials, scope='module')
|
||||
def fixture_panda_setup(request):
|
||||
"""
|
||||
Clean up all pandas + jungle and return the panda under test.
|
||||
"""
|
||||
panda_serial = request.param
|
||||
|
||||
# Initialize jungle
|
||||
init_jungle()
|
||||
|
||||
# Connect to pandas
|
||||
def cnnct(s):
|
||||
if s == panda_serial:
|
||||
p = Panda(serial=s)
|
||||
p.reset(reconnect=True)
|
||||
|
||||
p.set_can_loopback(False)
|
||||
p.set_power_save(False)
|
||||
for bus, speed in BUS_SPEEDS:
|
||||
p.set_can_speed_kbps(bus, speed)
|
||||
clear_can_buffers(p)
|
||||
p.set_power_save(False)
|
||||
return p
|
||||
elif not PARALLEL:
|
||||
with Panda(serial=s) as p:
|
||||
p.reset(reconnect=False)
|
||||
return None
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor() as exc:
|
||||
ps = list(exc.map(cnnct, _all_panda_serials, timeout=20))
|
||||
pandas = [p for p in ps if p is not None]
|
||||
|
||||
# run test
|
||||
yield pandas[0]
|
||||
|
||||
# Teardown
|
||||
for p in pandas:
|
||||
p.close()
|
||||
@@ -0,0 +1,71 @@
|
||||
import time
|
||||
import random
|
||||
|
||||
|
||||
def get_random_can_messages(n):
|
||||
m = []
|
||||
for _ in range(n):
|
||||
bus = random.randrange(3)
|
||||
addr = random.randrange(1 << 29)
|
||||
dat = bytes([random.getrandbits(8) for _ in range(random.randrange(1, 9))])
|
||||
m.append([addr, None, dat, bus])
|
||||
return m
|
||||
|
||||
|
||||
def time_many_sends(p, bus, p_recv=None, msg_count=100, two_pandas=False, msg_len=8):
|
||||
if p_recv is None:
|
||||
p_recv = p
|
||||
if p == p_recv and two_pandas:
|
||||
raise ValueError("Cannot have two pandas that are the same panda")
|
||||
|
||||
msg_id = random.randint(0x100, 0x200)
|
||||
to_send = [(msg_id, 0, b"\xaa" * msg_len, bus)] * msg_count
|
||||
|
||||
start_time = time.monotonic()
|
||||
p.can_send_many(to_send)
|
||||
r = []
|
||||
r_echo = []
|
||||
r_len_expected = msg_count if two_pandas else msg_count * 2
|
||||
r_echo_len_exected = msg_count if two_pandas else 0
|
||||
|
||||
while len(r) < r_len_expected and (time.monotonic() - start_time) < 5:
|
||||
r.extend(p_recv.can_recv())
|
||||
end_time = time.monotonic()
|
||||
if two_pandas:
|
||||
while len(r_echo) < r_echo_len_exected and (time.monotonic() - start_time) < 10:
|
||||
r_echo.extend(p.can_recv())
|
||||
|
||||
sent_echo = [x for x in r if x[3] == 0x80 | bus and x[0] == msg_id]
|
||||
sent_echo.extend([x for x in r_echo if x[3] == 0x80 | bus and x[0] == msg_id])
|
||||
resp = [x for x in r if x[3] == bus and x[0] == msg_id]
|
||||
|
||||
leftovers = [x for x in r if (x[3] != 0x80 | bus and x[3] != bus) or x[0] != msg_id]
|
||||
assert len(leftovers) == 0
|
||||
|
||||
assert len(resp) == msg_count
|
||||
assert len(sent_echo) == msg_count
|
||||
|
||||
end_time = (end_time - start_time) * 1000.0
|
||||
comp_kbps = (1 + 11 + 1 + 1 + 1 + 4 + (msg_len * 8) + 15 + 1 + 1 + 1 + 7) * msg_count / end_time
|
||||
|
||||
return comp_kbps
|
||||
|
||||
|
||||
def clear_can_buffers(panda, speed: int | None = None):
|
||||
if speed is not None:
|
||||
for bus in range(3):
|
||||
panda.set_can_speed_kbps(bus, speed)
|
||||
|
||||
# clear tx buffers
|
||||
for i in range(4):
|
||||
panda.can_clear(i)
|
||||
|
||||
# clear rx buffers
|
||||
panda.can_clear(0xFFFF)
|
||||
r = [1]
|
||||
st = time.monotonic()
|
||||
while len(r) > 0:
|
||||
r = panda.can_recv()
|
||||
time.sleep(0.05)
|
||||
if (time.monotonic() - st) > 10:
|
||||
raise Exception("Unable to clear can buffers for panda ", panda.get_serial())
|
||||
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
import concurrent.futures
|
||||
|
||||
from panda import PandaJungle, PandaJungleDFU, McuType
|
||||
from panda.tests.libs.resetter import Resetter
|
||||
|
||||
SERIALS = {'180019001451313236343430', '1d0017000c50435635333720'}
|
||||
|
||||
def recover(s):
|
||||
with PandaJungleDFU(s) as pd:
|
||||
pd.recover()
|
||||
|
||||
def flash(s):
|
||||
with PandaJungle(s) as p:
|
||||
p.flash()
|
||||
return p.get_mcu_type()
|
||||
|
||||
# Reset + flash all CI hardware to get it into a consistent state
|
||||
# * port 1: jungles-under-test
|
||||
# * port 2: USB hubs
|
||||
# * port 3: HITL pandas and their jungles
|
||||
if __name__ == "__main__":
|
||||
with Resetter() as r:
|
||||
# everything off
|
||||
for i in range(1, 4):
|
||||
r.enable_power(i, 0)
|
||||
r.cycle_power(ports=[1, 2], dfu=True)
|
||||
|
||||
dfu_serials = PandaJungleDFU.list()
|
||||
print(len(dfu_serials), len(SERIALS))
|
||||
assert len(dfu_serials) == len(SERIALS)
|
||||
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=len(dfu_serials)) as exc:
|
||||
list(exc.map(recover, dfu_serials, timeout=30))
|
||||
|
||||
# power cycle for H7 bootloader bug
|
||||
r.cycle_power(ports=[1, 2])
|
||||
|
||||
serials = PandaJungle.list()
|
||||
assert set(PandaJungle.list()) >= SERIALS
|
||||
mcu_types = list(exc.map(flash, SERIALS, timeout=20))
|
||||
assert set(mcu_types) == {McuType.F4, McuType.H7}
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
|
||||
cd $DIR
|
||||
|
||||
# n = number of pandas tested
|
||||
PARALLEL=1 pytest --durations=0 *.py -n 5 --dist loadgroup -x
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
|
||||
cd $DIR
|
||||
|
||||
NON_PARALLEL=1 pytest --durations=0 *.py -x
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python3
|
||||
import time
|
||||
|
||||
from panda import Panda
|
||||
|
||||
power = 0
|
||||
if __name__ == "__main__":
|
||||
p = Panda()
|
||||
while True:
|
||||
p.set_ir_power(power)
|
||||
print("Power: ", str(power))
|
||||
time.sleep(1)
|
||||
power += 10
|
||||
power %= 100
|
||||
@@ -0,0 +1,42 @@
|
||||
import platform
|
||||
|
||||
CC = 'gcc'
|
||||
system = platform.system()
|
||||
if system == 'Darwin':
|
||||
# gcc installed by homebrew has version suffix (e.g. gcc-12) in order to be
|
||||
# distinguishable from system one - which acts as a symlink to clang
|
||||
CC += '-13'
|
||||
|
||||
env = Environment(
|
||||
CC=CC,
|
||||
CFLAGS=[
|
||||
'-nostdlib',
|
||||
'-fno-builtin',
|
||||
'-std=gnu11',
|
||||
'-Wfatal-errors',
|
||||
'-Wno-pointer-to-int-cast',
|
||||
],
|
||||
CPPPATH=[".", "../../board/"],
|
||||
)
|
||||
if system == "Darwin":
|
||||
env.PrependENVPath('PATH', '/opt/homebrew/bin')
|
||||
|
||||
if GetOption('ubsan'):
|
||||
flags = [
|
||||
"-fsanitize=undefined",
|
||||
"-fno-sanitize-recover=undefined",
|
||||
]
|
||||
env['CFLAGS'] += flags
|
||||
env['LINKFLAGS'] += flags
|
||||
|
||||
panda = env.SharedObject("panda.os", "panda.c")
|
||||
libpanda = env.SharedLibrary("libpanda.so", [panda])
|
||||
|
||||
if GetOption('coverage'):
|
||||
env.Append(
|
||||
CFLAGS=["-fprofile-arcs", "-ftest-coverage", "-fprofile-abs-path",],
|
||||
LIBS=["gcov"],
|
||||
)
|
||||
# GCC note file is generated by compiler, ensure we build it, and allow scons to clean it up
|
||||
AlwaysBuild(panda)
|
||||
env.SideEffect("panda.gcno", panda)
|
||||
@@ -0,0 +1,96 @@
|
||||
import os
|
||||
from cffi import FFI
|
||||
from typing import Any, Protocol
|
||||
|
||||
from panda import LEN_TO_DLC
|
||||
from panda.tests.libpanda.safety_helpers import PandaSafety, setup_safety_helpers
|
||||
|
||||
libpanda_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
libpanda_fn = os.path.join(libpanda_dir, "libpanda.so")
|
||||
|
||||
ffi = FFI()
|
||||
|
||||
ffi.cdef("""
|
||||
typedef struct {
|
||||
unsigned char reserved : 1;
|
||||
unsigned char bus : 3;
|
||||
unsigned char data_len_code : 4;
|
||||
unsigned char rejected : 1;
|
||||
unsigned char returned : 1;
|
||||
unsigned char extended : 1;
|
||||
unsigned int addr : 29;
|
||||
unsigned char checksum;
|
||||
unsigned char data[64];
|
||||
} CANPacket_t;
|
||||
""", packed=True)
|
||||
|
||||
ffi.cdef("""
|
||||
bool safety_rx_hook(CANPacket_t *to_send);
|
||||
bool safety_tx_hook(CANPacket_t *to_push);
|
||||
int safety_fwd_hook(int bus_num, int addr);
|
||||
int set_safety_hooks(uint16_t mode, uint16_t param);
|
||||
""")
|
||||
|
||||
ffi.cdef("""
|
||||
typedef struct {
|
||||
volatile uint32_t w_ptr;
|
||||
volatile uint32_t r_ptr;
|
||||
uint32_t fifo_size;
|
||||
CANPacket_t *elems;
|
||||
} can_ring;
|
||||
|
||||
extern can_ring *rx_q;
|
||||
extern can_ring *tx1_q;
|
||||
extern can_ring *tx2_q;
|
||||
extern can_ring *tx3_q;
|
||||
|
||||
bool can_pop(can_ring *q, CANPacket_t *elem);
|
||||
bool can_push(can_ring *q, CANPacket_t *elem);
|
||||
void can_set_checksum(CANPacket_t *packet);
|
||||
int comms_can_read(uint8_t *data, uint32_t max_len);
|
||||
void comms_can_write(uint8_t *data, uint32_t len);
|
||||
void comms_can_reset(void);
|
||||
uint32_t can_slots_empty(can_ring *q);
|
||||
""")
|
||||
|
||||
setup_safety_helpers(ffi)
|
||||
|
||||
class CANPacket:
|
||||
reserved: int
|
||||
bus: int
|
||||
data_len_code: int
|
||||
rejected: int
|
||||
returned: int
|
||||
extended: int
|
||||
addr: int
|
||||
data: list[int]
|
||||
|
||||
class Panda(PandaSafety, Protocol):
|
||||
# CAN
|
||||
tx1_q: Any
|
||||
tx2_q: Any
|
||||
tx3_q: Any
|
||||
def can_set_checksum(self, p: CANPacket) -> None: ...
|
||||
|
||||
# safety
|
||||
def safety_rx_hook(self, to_send: CANPacket) -> int: ...
|
||||
def safety_tx_hook(self, to_push: CANPacket) -> int: ...
|
||||
def safety_fwd_hook(self, bus_num: int, addr: int) -> int: ...
|
||||
def set_safety_hooks(self, mode: int, param: int) -> int: ...
|
||||
|
||||
|
||||
libpanda: Panda = ffi.dlopen(libpanda_fn)
|
||||
|
||||
|
||||
# helpers
|
||||
|
||||
def make_CANPacket(addr: int, bus: int, dat):
|
||||
ret = ffi.new('CANPacket_t *')
|
||||
ret[0].extended = 1 if addr >= 0x800 else 0
|
||||
ret[0].addr = addr
|
||||
ret[0].data_len_code = LEN_TO_DLC[len(dat)]
|
||||
ret[0].bus = bus
|
||||
ret[0].data = bytes(dat)
|
||||
libpanda.can_set_checksum(ret)
|
||||
|
||||
return ret
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "fake_stm.h"
|
||||
#include "config.h"
|
||||
#include "can_definitions.h"
|
||||
|
||||
bool can_init(uint8_t can_number) { return true; }
|
||||
void process_can(uint8_t can_number) { }
|
||||
//int safety_tx_hook(CANPacket_t *to_send) { return 1; }
|
||||
|
||||
typedef struct harness_configuration harness_configuration;
|
||||
void refresh_can_tx_slots_available(void);
|
||||
void can_tx_comms_resume_usb(void) { };
|
||||
void can_tx_comms_resume_spi(void) { };
|
||||
|
||||
#include "health.h"
|
||||
#include "faults.h"
|
||||
#include "libc.h"
|
||||
#include "boards/board_declarations.h"
|
||||
#include "safety.h"
|
||||
#include "main_declarations.h"
|
||||
#include "drivers/can_common.h"
|
||||
|
||||
can_ring *rx_q = &can_rx_q;
|
||||
can_ring *tx1_q = &can_tx1_q;
|
||||
can_ring *tx2_q = &can_tx2_q;
|
||||
can_ring *tx3_q = &can_tx3_q;
|
||||
|
||||
#include "comms_definitions.h"
|
||||
#include "can_comms.h"
|
||||
|
||||
// libpanda stuff
|
||||
#include "safety_helpers.h"
|
||||
@@ -0,0 +1,191 @@
|
||||
void safety_tick_current_safety_config() {
|
||||
safety_tick(¤t_safety_config);
|
||||
}
|
||||
|
||||
bool safety_config_valid() {
|
||||
if (current_safety_config.rx_checks_len <= 0) {
|
||||
printf("missing RX checks\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < current_safety_config.rx_checks_len; i++) {
|
||||
const RxCheck addr = current_safety_config.rx_checks[i];
|
||||
bool valid = addr.status.msg_seen && !addr.status.lagging && addr.status.valid_checksum && (addr.status.wrong_counters < MAX_WRONG_COUNTERS) && addr.status.valid_quality_flag;
|
||||
if (!valid) {
|
||||
// printf("i %d seen %d lagging %d valid checksum %d wrong counters %d valid quality flag %d\n", i, addr.status.msg_seen, addr.status.lagging, addr.status.valid_checksum, addr.status.wrong_counters, addr.status.valid_quality_flag);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void set_controls_allowed(bool c){
|
||||
controls_allowed = c;
|
||||
}
|
||||
|
||||
void set_alternative_experience(int mode){
|
||||
alternative_experience = mode;
|
||||
}
|
||||
|
||||
void set_relay_malfunction(bool c){
|
||||
relay_malfunction = c;
|
||||
}
|
||||
|
||||
bool get_controls_allowed(void){
|
||||
return controls_allowed;
|
||||
}
|
||||
|
||||
int get_alternative_experience(void){
|
||||
return alternative_experience;
|
||||
}
|
||||
|
||||
bool get_relay_malfunction(void){
|
||||
return relay_malfunction;
|
||||
}
|
||||
|
||||
bool get_gas_pressed_prev(void){
|
||||
return gas_pressed_prev;
|
||||
}
|
||||
|
||||
bool get_brake_pressed_prev(void){
|
||||
return brake_pressed_prev;
|
||||
}
|
||||
|
||||
bool get_regen_braking_prev(void){
|
||||
return regen_braking_prev;
|
||||
}
|
||||
|
||||
bool get_cruise_engaged_prev(void){
|
||||
return cruise_engaged_prev;
|
||||
}
|
||||
|
||||
void set_cruise_engaged_prev(bool engaged){
|
||||
cruise_engaged_prev = engaged;
|
||||
}
|
||||
|
||||
bool get_vehicle_moving(void){
|
||||
return vehicle_moving;
|
||||
}
|
||||
|
||||
bool get_acc_main_on(void){
|
||||
return acc_main_on;
|
||||
}
|
||||
|
||||
int get_vehicle_speed_min(void){
|
||||
return vehicle_speed.min;
|
||||
}
|
||||
|
||||
int get_vehicle_speed_max(void){
|
||||
return vehicle_speed.max;
|
||||
}
|
||||
|
||||
int get_vehicle_speed_last(void){
|
||||
return vehicle_speed.values[0];
|
||||
}
|
||||
|
||||
int get_current_safety_mode(void){
|
||||
return current_safety_mode;
|
||||
}
|
||||
|
||||
int get_current_safety_param(void){
|
||||
return current_safety_param;
|
||||
}
|
||||
|
||||
int get_hw_type(void){
|
||||
return hw_type;
|
||||
}
|
||||
|
||||
void set_timer(uint32_t t){
|
||||
timer.CNT = t;
|
||||
}
|
||||
|
||||
void set_torque_meas(int min, int max){
|
||||
torque_meas.min = min;
|
||||
torque_meas.max = max;
|
||||
}
|
||||
|
||||
int get_torque_meas_min(void){
|
||||
return torque_meas.min;
|
||||
}
|
||||
|
||||
int get_torque_meas_max(void){
|
||||
return torque_meas.max;
|
||||
}
|
||||
|
||||
void set_torque_driver(int min, int max){
|
||||
torque_driver.min = min;
|
||||
torque_driver.max = max;
|
||||
}
|
||||
|
||||
int get_torque_driver_min(void){
|
||||
return torque_driver.min;
|
||||
}
|
||||
|
||||
int get_torque_driver_max(void){
|
||||
return torque_driver.max;
|
||||
}
|
||||
|
||||
void set_rt_torque_last(int t){
|
||||
rt_torque_last = t;
|
||||
}
|
||||
|
||||
void set_desired_torque_last(int t){
|
||||
desired_torque_last = t;
|
||||
}
|
||||
|
||||
void set_desired_angle_last(int t){
|
||||
desired_angle_last = t;
|
||||
}
|
||||
|
||||
int get_desired_angle_last(void){
|
||||
return desired_angle_last;
|
||||
}
|
||||
|
||||
void set_angle_meas(int min, int max){
|
||||
angle_meas.min = min;
|
||||
angle_meas.max = max;
|
||||
}
|
||||
|
||||
int get_angle_meas_min(void){
|
||||
return angle_meas.min;
|
||||
}
|
||||
|
||||
int get_angle_meas_max(void){
|
||||
return angle_meas.max;
|
||||
}
|
||||
|
||||
|
||||
// ***** car specific helpers *****
|
||||
|
||||
void set_honda_alt_brake_msg(bool c){
|
||||
honda_alt_brake_msg = c;
|
||||
}
|
||||
|
||||
void set_honda_bosch_long(bool c){
|
||||
honda_bosch_long = c;
|
||||
}
|
||||
|
||||
int get_honda_hw(void) {
|
||||
return honda_hw;
|
||||
}
|
||||
|
||||
void set_honda_fwd_brake(bool c){
|
||||
honda_fwd_brake = c;
|
||||
}
|
||||
|
||||
bool get_honda_fwd_brake(void){
|
||||
return honda_fwd_brake;
|
||||
}
|
||||
|
||||
void init_tests(void){
|
||||
// get HW_TYPE from env variable set in test.sh
|
||||
if (getenv("HW_TYPE")) {
|
||||
hw_type = atoi(getenv("HW_TYPE"));
|
||||
}
|
||||
safety_mode_cnt = 2U; // avoid ignoring relay_malfunction logic
|
||||
alternative_experience = 0;
|
||||
set_timer(0);
|
||||
ts_steer_req_mismatch_last = 0;
|
||||
valid_steer_req_count = 0;
|
||||
invalid_steer_req_count = 0;
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
# panda safety helpers, from safety_helpers.c
|
||||
from typing import Protocol
|
||||
|
||||
def setup_safety_helpers(ffi):
|
||||
ffi.cdef("""
|
||||
void set_controls_allowed(bool c);
|
||||
bool get_controls_allowed(void);
|
||||
bool get_longitudinal_allowed(void);
|
||||
void set_alternative_experience(int mode);
|
||||
int get_alternative_experience(void);
|
||||
void set_relay_malfunction(bool c);
|
||||
bool get_relay_malfunction(void);
|
||||
bool get_gas_pressed_prev(void);
|
||||
bool get_brake_pressed_prev(void);
|
||||
bool get_regen_braking_prev(void);
|
||||
bool get_acc_main_on(void);
|
||||
int get_vehicle_speed_min(void);
|
||||
int get_vehicle_speed_max(void);
|
||||
int get_vehicle_speed_last(void);
|
||||
int get_current_safety_mode(void);
|
||||
int get_current_safety_param(void);
|
||||
|
||||
void set_torque_meas(int min, int max);
|
||||
int get_torque_meas_min(void);
|
||||
int get_torque_meas_max(void);
|
||||
void set_torque_driver(int min, int max);
|
||||
int get_torque_driver_min(void);
|
||||
int get_torque_driver_max(void);
|
||||
void set_desired_torque_last(int t);
|
||||
void set_rt_torque_last(int t);
|
||||
void set_desired_angle_last(int t);
|
||||
int get_desired_angle_last();
|
||||
void set_angle_meas(int min, int max);
|
||||
int get_angle_meas_min(void);
|
||||
int get_angle_meas_max(void);
|
||||
|
||||
bool get_cruise_engaged_prev(void);
|
||||
void set_cruise_engaged_prev(bool engaged);
|
||||
bool get_vehicle_moving(void);
|
||||
int get_hw_type(void);
|
||||
void set_timer(uint32_t t);
|
||||
|
||||
void safety_tick_current_safety_config();
|
||||
bool safety_config_valid();
|
||||
|
||||
void init_tests(void);
|
||||
|
||||
void set_honda_fwd_brake(bool c);
|
||||
bool get_honda_fwd_brake(void);
|
||||
void set_honda_alt_brake_msg(bool c);
|
||||
void set_honda_bosch_long(bool c);
|
||||
int get_honda_hw(void);
|
||||
""")
|
||||
|
||||
class PandaSafety(Protocol):
|
||||
def set_controls_allowed(self, c: bool) -> None: ...
|
||||
def get_controls_allowed(self) -> bool: ...
|
||||
def get_longitudinal_allowed(self) -> bool: ...
|
||||
def set_alternative_experience(self, mode: int) -> None: ...
|
||||
def get_alternative_experience(self) -> int: ...
|
||||
def set_relay_malfunction(self, c: bool) -> None: ...
|
||||
def get_relay_malfunction(self) -> bool: ...
|
||||
def get_gas_pressed_prev(self) -> bool: ...
|
||||
def get_brake_pressed_prev(self) -> bool: ...
|
||||
def get_regen_braking_prev(self) -> bool: ...
|
||||
def get_acc_main_on(self) -> bool: ...
|
||||
def get_vehicle_speed_min(self) -> int: ...
|
||||
def get_vehicle_speed_max(self) -> int: ...
|
||||
def get_vehicle_speed_last(self) -> int: ...
|
||||
def get_current_safety_mode(self) -> int: ...
|
||||
def get_current_safety_param(self) -> int: ...
|
||||
|
||||
def set_torque_meas(self, min: int, max: int) -> None: ... # noqa: A002
|
||||
def get_torque_meas_min(self) -> int: ...
|
||||
def get_torque_meas_max(self) -> int: ...
|
||||
def set_torque_driver(self, min: int, max: int) -> None: ... # noqa: A002
|
||||
def get_torque_driver_min(self) -> int: ...
|
||||
def get_torque_driver_max(self) -> int: ...
|
||||
def set_desired_torque_last(self, t: int) -> None: ...
|
||||
def set_rt_torque_last(self, t: int) -> None: ...
|
||||
def set_desired_angle_last(self, t: int) -> None: ...
|
||||
def get_desired_angle_last(self) -> int: ...
|
||||
def set_angle_meas(self, min: int, max: int) -> None: ... # noqa: A002
|
||||
def get_angle_meas_min(self) -> int: ...
|
||||
def get_angle_meas_max(self) -> int: ...
|
||||
|
||||
def get_cruise_engaged_prev(self) -> bool: ...
|
||||
def set_cruise_engaged_prev(self, enabled: bool) -> None: ...
|
||||
def get_vehicle_moving(self) -> bool: ...
|
||||
def get_hw_type(self) -> int: ...
|
||||
def set_timer(self, t: int) -> None: ...
|
||||
|
||||
def safety_tick_current_safety_config(self) -> None: ...
|
||||
def safety_config_valid(self) -> bool: ...
|
||||
|
||||
def init_tests(self) -> None: ...
|
||||
|
||||
def set_honda_fwd_brake(self, c: bool) -> None: ...
|
||||
def get_honda_fwd_brake(self) -> bool: ...
|
||||
def set_honda_alt_brake_msg(self, c: bool) -> None: ...
|
||||
def set_honda_bosch_long(self, c: bool) -> None: ...
|
||||
def get_honda_hw(self) -> int: ...
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import time
|
||||
import usb1
|
||||
|
||||
|
||||
class Resetter():
|
||||
def __init__(self):
|
||||
self._handle = None
|
||||
self.connect()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.close()
|
||||
|
||||
def close(self):
|
||||
self._handle.close()
|
||||
self._context.close()
|
||||
self._handle = None
|
||||
|
||||
def connect(self):
|
||||
if self._handle:
|
||||
self.close()
|
||||
|
||||
self._handle = None
|
||||
|
||||
self._context = usb1.USBContext()
|
||||
self._context.open()
|
||||
for device in self._context.getDeviceList(skip_on_error=True):
|
||||
if device.getVendorID() == 0xbbaa and device.getProductID() == 0xddc0:
|
||||
try:
|
||||
self._handle = device.open()
|
||||
self._handle.claimInterface(0)
|
||||
break
|
||||
except Exception as e:
|
||||
print(e)
|
||||
assert self._handle
|
||||
|
||||
def enable_power(self, port, enabled):
|
||||
self._handle.controlWrite((usb1.ENDPOINT_OUT | usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE), 0xff, port, enabled, b'')
|
||||
|
||||
def enable_boot(self, enabled):
|
||||
self._handle.controlWrite((usb1.ENDPOINT_OUT | usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE), 0xff, 0, enabled, b'')
|
||||
|
||||
def cycle_power(self, delay=5, dfu=False, ports=None):
|
||||
if ports is None:
|
||||
ports = [1, 2, 3]
|
||||
|
||||
self.enable_boot(dfu)
|
||||
for port in ports:
|
||||
self.enable_power(port, False)
|
||||
time.sleep(0.5)
|
||||
|
||||
for port in ports:
|
||||
self.enable_power(port, True)
|
||||
time.sleep(delay)
|
||||
self.enable_boot(False)
|
||||
Executable
+94
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
import argparse
|
||||
from itertools import permutations
|
||||
|
||||
from panda import Panda
|
||||
|
||||
def get_test_string():
|
||||
return b"test" + os.urandom(10)
|
||||
|
||||
def run_test(sleep_duration):
|
||||
pandas = Panda.list()
|
||||
print(pandas)
|
||||
|
||||
if len(pandas) < 2:
|
||||
raise Exception("Minimum two pandas are needed for test")
|
||||
|
||||
run_test_w_pandas(pandas, sleep_duration)
|
||||
|
||||
def run_test_w_pandas(pandas, sleep_duration):
|
||||
h = [Panda(x) for x in pandas]
|
||||
print("H", h)
|
||||
|
||||
for hh in h:
|
||||
hh.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
|
||||
# test both directions
|
||||
for ho in permutations(list(range(len(h))), r=2):
|
||||
print("***************** TESTING", ho)
|
||||
|
||||
panda0, panda1 = h[ho[0]], h[ho[1]]
|
||||
|
||||
# **** test health packet ****
|
||||
print("health", ho[0], h[ho[0]].health())
|
||||
|
||||
# **** test can line loopback ****
|
||||
for bus, obd in [(0, False), (1, False), (2, False), (1, True), (2, True)]:
|
||||
print("\ntest can", bus)
|
||||
# flush
|
||||
cans_echo = panda0.can_recv()
|
||||
cans_loop = panda1.can_recv()
|
||||
|
||||
panda0.set_obd(None)
|
||||
panda1.set_obd(None)
|
||||
|
||||
if obd is True:
|
||||
panda0.set_obd(bus)
|
||||
panda1.set_obd(bus)
|
||||
bus = 3
|
||||
|
||||
# send the characters
|
||||
at = random.randint(1, 2000)
|
||||
st = get_test_string()[0:8]
|
||||
panda0.can_send(at, st, bus)
|
||||
time.sleep(0.1)
|
||||
|
||||
# check for receive
|
||||
cans_echo = panda0.can_recv()
|
||||
cans_loop = panda1.can_recv()
|
||||
|
||||
print("Bus", bus, "echo", cans_echo, "loop", cans_loop)
|
||||
|
||||
assert len(cans_echo) == 1
|
||||
assert len(cans_loop) == 1
|
||||
|
||||
assert cans_echo[0][0] == at
|
||||
assert cans_loop[0][0] == at
|
||||
|
||||
assert cans_echo[0][2] == st
|
||||
assert cans_loop[0][2] == st
|
||||
|
||||
assert cans_echo[0][3] == 0x80 | bus
|
||||
if cans_loop[0][3] != bus:
|
||||
print("EXPECTED %d GOT %d" % (bus, cans_loop[0][3]))
|
||||
assert cans_loop[0][3] == bus
|
||||
|
||||
print("CAN pass", bus, ho)
|
||||
time.sleep(sleep_duration)
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-n", type=int, help="Number of test iterations to run")
|
||||
parser.add_argument("-sleep", type=int, help="Sleep time between tests", default=0)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.n is None:
|
||||
while True:
|
||||
run_test(sleep_duration=args.sleep)
|
||||
else:
|
||||
for _ in range(args.n):
|
||||
run_test(sleep_duration=args.sleep)
|
||||
Executable
+70
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import usb1
|
||||
import time
|
||||
import struct
|
||||
import itertools
|
||||
import threading
|
||||
from typing import Any
|
||||
|
||||
from panda import Panda
|
||||
|
||||
JUNGLE = "JUNGLE" in os.environ
|
||||
if JUNGLE:
|
||||
from panda import PandaJungle
|
||||
|
||||
# Generate unique messages
|
||||
NUM_MESSAGES_PER_BUS = 10000
|
||||
messages = [bytes(struct.pack("Q", i)) for i in range(NUM_MESSAGES_PER_BUS)]
|
||||
tx_messages = list(itertools.chain.from_iterable([[0xaa, None, msg, 0], [0xaa, None, msg, 1], [0xaa, None, msg, 2]] for msg in messages))
|
||||
|
||||
def flood_tx(panda):
|
||||
print('Sending!')
|
||||
transferred = 0
|
||||
while True:
|
||||
try:
|
||||
print(f"Sending block {transferred}-{len(tx_messages)}: ", end="")
|
||||
panda.can_send_many(tx_messages[transferred:], timeout=10)
|
||||
print("OK")
|
||||
break
|
||||
except usb1.USBErrorTimeout as e:
|
||||
transferred += (e.transferred // 16)
|
||||
print("timeout, transferred: ", transferred)
|
||||
|
||||
print(f"Done sending {3*NUM_MESSAGES_PER_BUS} messages!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
serials = Panda.list()
|
||||
receiver: Panda | PandaJungle
|
||||
if JUNGLE:
|
||||
sender = Panda()
|
||||
receiver = PandaJungle()
|
||||
else:
|
||||
if len(serials) != 2:
|
||||
raise Exception("Connect two pandas to perform this test!")
|
||||
sender = Panda(serials[0])
|
||||
receiver = Panda(serials[1])
|
||||
receiver.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
|
||||
sender.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
|
||||
# Start transmisson
|
||||
threading.Thread(target=flood_tx, args=(sender,)).start()
|
||||
|
||||
# Receive as much as we can, and stop when there hasn't been anything for a second
|
||||
rx: list[Any] = []
|
||||
old_len = 0
|
||||
last_change = time.monotonic()
|
||||
while time.monotonic() - last_change < 1:
|
||||
if old_len < len(rx):
|
||||
last_change = time.monotonic()
|
||||
old_len = len(rx)
|
||||
|
||||
rx.extend(receiver.can_recv())
|
||||
print(f"Received {len(rx)} messages")
|
||||
|
||||
# Check if we received everything
|
||||
for bus in range(3):
|
||||
received_msgs = {bytes(m[2]) for m in filter(lambda m, b=bus: m[3] == b, rx)} # type: ignore
|
||||
dropped_msgs = set(messages).difference(received_msgs)
|
||||
print(f"Bus {bus} dropped msgs: {len(list(dropped_msgs))} / {len(messages)}")
|
||||
@@ -0,0 +1,5 @@
|
||||
*.pdf
|
||||
*.txt
|
||||
.output.log
|
||||
new_table
|
||||
cppcheck/
|
||||
@@ -0,0 +1,867 @@
|
||||
Cppcheck checkers list from test_misra.sh:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
TEST variant options:
|
||||
--enable=all --disable=unusedFunction -DPANDA --addon=misra -DSTM32F4 -DSTM32F413xx /board/main.c
|
||||
|
||||
|
||||
Critical errors
|
||||
---------------
|
||||
No critical errors, all files were checked.
|
||||
Important: Analysis is still not guaranteed to be 'complete' it is possible there are false negatives.
|
||||
|
||||
|
||||
Open source checkers
|
||||
--------------------
|
||||
Yes Check64BitPortability::pointerassignment
|
||||
Yes CheckAssert::assertWithSideEffects
|
||||
Yes CheckAutoVariables::assignFunctionArg
|
||||
Yes CheckAutoVariables::autoVariables
|
||||
Yes CheckAutoVariables::checkVarLifetime
|
||||
No CheckBool::checkAssignBoolToFloat require:style,c++
|
||||
Yes CheckBool::checkAssignBoolToPointer
|
||||
No CheckBool::checkBitwiseOnBoolean require:style,inconclusive
|
||||
Yes CheckBool::checkComparisonOfBoolExpressionWithInt
|
||||
No CheckBool::checkComparisonOfBoolWithBool require:style,c++
|
||||
No CheckBool::checkComparisonOfBoolWithInt require:warning,c++
|
||||
No CheckBool::checkComparisonOfFuncReturningBool require:style,c++
|
||||
Yes CheckBool::checkIncrementBoolean
|
||||
Yes CheckBool::pointerArithBool
|
||||
Yes CheckBool::returnValueOfFunctionReturningBool
|
||||
No CheckBoost::checkBoostForeachModification
|
||||
Yes CheckBufferOverrun::analyseWholeProgram
|
||||
Yes CheckBufferOverrun::argumentSize
|
||||
Yes CheckBufferOverrun::arrayIndex
|
||||
Yes CheckBufferOverrun::arrayIndexThenCheck
|
||||
Yes CheckBufferOverrun::bufferOverflow
|
||||
Yes CheckBufferOverrun::negativeArraySize
|
||||
Yes CheckBufferOverrun::objectIndex
|
||||
Yes CheckBufferOverrun::pointerArithmetic
|
||||
No CheckBufferOverrun::stringNotZeroTerminated require:warning,inconclusive
|
||||
Yes CheckClass::analyseWholeProgram
|
||||
No CheckClass::checkConst require:style,inconclusive
|
||||
No CheckClass::checkConstructors require:style,warning
|
||||
No CheckClass::checkCopyConstructors require:warning
|
||||
No CheckClass::checkDuplInheritedMembers require:warning
|
||||
No CheckClass::checkExplicitConstructors require:style
|
||||
No CheckClass::checkMemset
|
||||
No CheckClass::checkMissingOverride require:style,c++03
|
||||
No CheckClass::checkReturnByReference require:performance
|
||||
No CheckClass::checkSelfInitialization
|
||||
No CheckClass::checkThisUseAfterFree require:warning
|
||||
No CheckClass::checkUnsafeClassRefMember require:warning,safeChecks
|
||||
No CheckClass::checkUselessOverride require:style
|
||||
No CheckClass::checkVirtualFunctionCallInConstructor require:warning
|
||||
No CheckClass::initializationListUsage require:performance
|
||||
No CheckClass::initializerListOrder require:style,inconclusive
|
||||
No CheckClass::operatorEqRetRefThis require:style
|
||||
No CheckClass::operatorEqToSelf require:warning
|
||||
No CheckClass::privateFunctions require:style
|
||||
No CheckClass::thisSubtraction require:warning
|
||||
No CheckClass::virtualDestructor
|
||||
Yes CheckCondition::alwaysTrueFalse
|
||||
Yes CheckCondition::assignIf
|
||||
Yes CheckCondition::checkAssignmentInCondition
|
||||
Yes CheckCondition::checkBadBitmaskCheck
|
||||
Yes CheckCondition::checkCompareValueOutOfTypeRange
|
||||
Yes CheckCondition::checkDuplicateConditionalAssign
|
||||
Yes CheckCondition::checkIncorrectLogicOperator
|
||||
Yes CheckCondition::checkInvalidTestForOverflow
|
||||
Yes CheckCondition::checkModuloAlwaysTrueFalse
|
||||
Yes CheckCondition::checkPointerAdditionResultNotNull
|
||||
Yes CheckCondition::clarifyCondition
|
||||
Yes CheckCondition::comparison
|
||||
Yes CheckCondition::duplicateCondition
|
||||
Yes CheckCondition::multiCondition
|
||||
Yes CheckCondition::multiCondition2
|
||||
No CheckExceptionSafety::checkCatchExceptionByValue require:style
|
||||
No CheckExceptionSafety::checkRethrowCopy require:style
|
||||
No CheckExceptionSafety::deallocThrow require:warning
|
||||
No CheckExceptionSafety::destructors require:warning
|
||||
No CheckExceptionSafety::nothrowThrows
|
||||
No CheckExceptionSafety::rethrowNoCurrentException
|
||||
No CheckExceptionSafety::unhandledExceptionSpecification require:style,inconclusive
|
||||
Yes CheckFunctions::checkIgnoredReturnValue
|
||||
Yes CheckFunctions::checkMathFunctions
|
||||
Yes CheckFunctions::checkMissingReturn
|
||||
Yes CheckFunctions::checkProhibitedFunctions
|
||||
Yes CheckFunctions::invalidFunctionUsage
|
||||
Yes CheckFunctions::memsetInvalid2ndParam
|
||||
Yes CheckFunctions::memsetZeroBytes
|
||||
No CheckFunctions::returnLocalStdMove require:performance,c++11
|
||||
Yes CheckFunctions::useStandardLibrary
|
||||
No CheckIO::checkCoutCerrMisusage require:c
|
||||
Yes CheckIO::checkFileUsage
|
||||
Yes CheckIO::checkWrongPrintfScanfArguments
|
||||
Yes CheckIO::invalidScanf
|
||||
Yes CheckLeakAutoVar::check
|
||||
No CheckMemoryLeakInClass::check
|
||||
Yes CheckMemoryLeakInFunction::checkReallocUsage
|
||||
Yes CheckMemoryLeakNoVar::check
|
||||
No CheckMemoryLeakNoVar::checkForUnsafeArgAlloc
|
||||
Yes CheckMemoryLeakStructMember::check
|
||||
Yes CheckNullPointer::analyseWholeProgram
|
||||
Yes CheckNullPointer::arithmetic
|
||||
Yes CheckNullPointer::nullConstantDereference
|
||||
Yes CheckNullPointer::nullPointer
|
||||
No CheckOther::checkAccessOfMovedVariable require:c++11,warning
|
||||
Yes CheckOther::checkCastIntToCharAndBack
|
||||
Yes CheckOther::checkCharVariable
|
||||
Yes CheckOther::checkComparePointers
|
||||
Yes CheckOther::checkComparisonFunctionIsAlwaysTrueOrFalse
|
||||
Yes CheckOther::checkConstPointer
|
||||
No CheckOther::checkConstVariable require:style,c++
|
||||
No CheckOther::checkDuplicateBranch require:style,inconclusive
|
||||
Yes CheckOther::checkDuplicateExpression
|
||||
Yes CheckOther::checkEvaluationOrder
|
||||
Yes CheckOther::checkFuncArgNamesDifferent
|
||||
No CheckOther::checkIncompleteArrayFill require:warning,portability,inconclusive
|
||||
Yes CheckOther::checkIncompleteStatement
|
||||
No CheckOther::checkInterlockedDecrement require:windows-platform
|
||||
Yes CheckOther::checkInvalidFree
|
||||
Yes CheckOther::checkKnownArgument
|
||||
Yes CheckOther::checkKnownPointerToBool
|
||||
No CheckOther::checkMisusedScopedObject require:style,c++
|
||||
Yes CheckOther::checkModuloOfOne
|
||||
Yes CheckOther::checkNanInArithmeticExpression
|
||||
Yes CheckOther::checkNegativeBitwiseShift
|
||||
Yes CheckOther::checkOverlappingWrite
|
||||
No CheckOther::checkPassByReference require:performance,c++
|
||||
Yes CheckOther::checkRedundantAssignment
|
||||
No CheckOther::checkRedundantCopy require:c++,performance,inconclusive
|
||||
Yes CheckOther::checkRedundantPointerOp
|
||||
Yes CheckOther::checkShadowVariables
|
||||
Yes CheckOther::checkSignOfUnsignedVariable
|
||||
No CheckOther::checkSuspiciousCaseInSwitch require:warning,inconclusive
|
||||
No CheckOther::checkSuspiciousSemicolon require:warning,inconclusive
|
||||
Yes CheckOther::checkUnreachableCode
|
||||
Yes CheckOther::checkUnusedLabel
|
||||
Yes CheckOther::checkVarFuncNullUB
|
||||
Yes CheckOther::checkVariableScope
|
||||
Yes CheckOther::checkZeroDivision
|
||||
Yes CheckOther::clarifyCalculation
|
||||
Yes CheckOther::clarifyStatement
|
||||
Yes CheckOther::invalidPointerCast
|
||||
Yes CheckOther::redundantBitwiseOperationInSwitch
|
||||
No CheckOther::warningOldStylePointerCast require:style,c++
|
||||
No CheckPostfixOperator::postfixOperator require:performance
|
||||
Yes CheckSizeof::checkSizeofForArrayParameter
|
||||
Yes CheckSizeof::checkSizeofForNumericParameter
|
||||
Yes CheckSizeof::checkSizeofForPointerSize
|
||||
Yes CheckSizeof::sizeofCalculation
|
||||
Yes CheckSizeof::sizeofFunction
|
||||
Yes CheckSizeof::sizeofVoid
|
||||
Yes CheckSizeof::sizeofsizeof
|
||||
No CheckSizeof::suspiciousSizeofCalculation require:warning,inconclusive
|
||||
No CheckStl::checkDereferenceInvalidIterator require:warning
|
||||
No CheckStl::checkDereferenceInvalidIterator2
|
||||
No CheckStl::checkFindInsert require:performance
|
||||
No CheckStl::checkMutexes require:warning
|
||||
No CheckStl::erase
|
||||
No CheckStl::eraseIteratorOutOfBounds
|
||||
No CheckStl::if_find require:warning,performance
|
||||
No CheckStl::invalidContainer
|
||||
No CheckStl::iterators
|
||||
No CheckStl::knownEmptyContainer require:style
|
||||
No CheckStl::misMatchingContainerIterator
|
||||
No CheckStl::misMatchingContainers
|
||||
No CheckStl::missingComparison require:warning
|
||||
No CheckStl::negativeIndex
|
||||
No CheckStl::outOfBounds
|
||||
No CheckStl::outOfBoundsIndexExpression
|
||||
No CheckStl::redundantCondition require:style
|
||||
No CheckStl::size require:performance,c++03
|
||||
No CheckStl::stlBoundaries
|
||||
No CheckStl::stlOutOfBounds
|
||||
No CheckStl::string_c_str
|
||||
No CheckStl::useStlAlgorithm require:style
|
||||
No CheckStl::uselessCalls require:performance,warning
|
||||
Yes CheckString::checkAlwaysTrueOrFalseStringCompare
|
||||
Yes CheckString::checkIncorrectStringCompare
|
||||
Yes CheckString::checkSuspiciousStringCompare
|
||||
Yes CheckString::overlappingStrcmp
|
||||
Yes CheckString::sprintfOverlappingData
|
||||
Yes CheckString::strPlusChar
|
||||
Yes CheckString::stringLiteralWrite
|
||||
Yes CheckType::checkFloatToIntegerOverflow
|
||||
Yes CheckType::checkIntegerOverflow
|
||||
Yes CheckType::checkLongCast
|
||||
Yes CheckType::checkSignConversion
|
||||
Yes CheckType::checkTooBigBitwiseShift
|
||||
Yes CheckUninitVar::check
|
||||
Yes CheckUninitVar::valueFlowUninit
|
||||
No CheckUnusedFunctions::check require:unusedFunction
|
||||
Yes CheckUnusedVar::checkFunctionVariableUsage
|
||||
Yes CheckUnusedVar::checkStructMemberUsage
|
||||
Yes CheckVaarg::va_list_usage
|
||||
Yes CheckVaarg::va_start_argument
|
||||
|
||||
|
||||
Premium checkers
|
||||
----------------
|
||||
Not available, Cppcheck Premium is not used
|
||||
|
||||
|
||||
Autosar
|
||||
-------
|
||||
Not available, Cppcheck Premium is not used
|
||||
|
||||
|
||||
Cert C
|
||||
------
|
||||
Not available, Cppcheck Premium is not used
|
||||
|
||||
|
||||
Cert C++
|
||||
--------
|
||||
Not available, Cppcheck Premium is not used
|
||||
|
||||
|
||||
Misra C 2012
|
||||
------------
|
||||
Yes Misra C 2012: 1.1
|
||||
Yes Misra C 2012: 1.2
|
||||
Yes Misra C 2012: 1.3
|
||||
Yes Misra C 2012: 1.4 amendment:2
|
||||
No Misra C 2012: 1.5 amendment:3 require:premium
|
||||
Yes Misra C 2012: 2.1
|
||||
Yes Misra C 2012: 2.2
|
||||
Yes Misra C 2012: 2.3
|
||||
Yes Misra C 2012: 2.4
|
||||
Yes Misra C 2012: 2.5
|
||||
Yes Misra C 2012: 2.6
|
||||
Yes Misra C 2012: 2.7
|
||||
Yes Misra C 2012: 2.8
|
||||
Yes Misra C 2012: 3.1
|
||||
Yes Misra C 2012: 3.2
|
||||
Yes Misra C 2012: 4.1
|
||||
Yes Misra C 2012: 4.2
|
||||
Yes Misra C 2012: 5.1
|
||||
Yes Misra C 2012: 5.2
|
||||
Yes Misra C 2012: 5.3
|
||||
Yes Misra C 2012: 5.4
|
||||
Yes Misra C 2012: 5.5
|
||||
Yes Misra C 2012: 5.6
|
||||
Yes Misra C 2012: 5.7
|
||||
Yes Misra C 2012: 5.8
|
||||
Yes Misra C 2012: 5.9
|
||||
Yes Misra C 2012: 6.1
|
||||
Yes Misra C 2012: 6.2
|
||||
No Misra C 2012: 6.3
|
||||
Yes Misra C 2012: 7.1
|
||||
Yes Misra C 2012: 7.2
|
||||
Yes Misra C 2012: 7.3
|
||||
Yes Misra C 2012: 7.4
|
||||
No Misra C 2012: 7.5
|
||||
No Misra C 2012: 7.6
|
||||
Yes Misra C 2012: 8.1
|
||||
Yes Misra C 2012: 8.2
|
||||
No Misra C 2012: 8.3
|
||||
Yes Misra C 2012: 8.4
|
||||
Yes Misra C 2012: 8.5
|
||||
Yes Misra C 2012: 8.6
|
||||
Yes Misra C 2012: 8.7
|
||||
Yes Misra C 2012: 8.8
|
||||
Yes Misra C 2012: 8.9
|
||||
Yes Misra C 2012: 8.10
|
||||
Yes Misra C 2012: 8.11
|
||||
Yes Misra C 2012: 8.12
|
||||
Yes Misra C 2012: 8.13
|
||||
Yes Misra C 2012: 8.14
|
||||
No Misra C 2012: 8.15
|
||||
No Misra C 2012: 8.16
|
||||
No Misra C 2012: 8.17
|
||||
Yes Misra C 2012: 9.1
|
||||
Yes Misra C 2012: 9.2
|
||||
Yes Misra C 2012: 9.3
|
||||
Yes Misra C 2012: 9.4
|
||||
Yes Misra C 2012: 9.5
|
||||
No Misra C 2012: 9.6
|
||||
No Misra C 2012: 9.7
|
||||
Yes Misra C 2012: 10.1
|
||||
Yes Misra C 2012: 10.2
|
||||
Yes Misra C 2012: 10.3
|
||||
Yes Misra C 2012: 10.4
|
||||
Yes Misra C 2012: 10.5
|
||||
Yes Misra C 2012: 10.6
|
||||
Yes Misra C 2012: 10.7
|
||||
Yes Misra C 2012: 10.8
|
||||
Yes Misra C 2012: 11.1
|
||||
Yes Misra C 2012: 11.2
|
||||
Yes Misra C 2012: 11.3
|
||||
Yes Misra C 2012: 11.4
|
||||
Yes Misra C 2012: 11.5
|
||||
Yes Misra C 2012: 11.6
|
||||
Yes Misra C 2012: 11.7
|
||||
Yes Misra C 2012: 11.8
|
||||
Yes Misra C 2012: 11.9
|
||||
No Misra C 2012: 11.10
|
||||
Yes Misra C 2012: 12.1
|
||||
Yes Misra C 2012: 12.2
|
||||
Yes Misra C 2012: 12.3
|
||||
Yes Misra C 2012: 12.4
|
||||
Yes Misra C 2012: 12.5 amendment:1
|
||||
No Misra C 2012: 12.6 amendment:4 require:premium
|
||||
Yes Misra C 2012: 13.1
|
||||
No Misra C 2012: 13.2
|
||||
Yes Misra C 2012: 13.3
|
||||
Yes Misra C 2012: 13.4
|
||||
Yes Misra C 2012: 13.5
|
||||
Yes Misra C 2012: 13.6
|
||||
Yes Misra C 2012: 14.1
|
||||
Yes Misra C 2012: 14.2
|
||||
Yes Misra C 2012: 14.3
|
||||
Yes Misra C 2012: 14.4
|
||||
Yes Misra C 2012: 15.1
|
||||
Yes Misra C 2012: 15.2
|
||||
Yes Misra C 2012: 15.3
|
||||
Yes Misra C 2012: 15.4
|
||||
Yes Misra C 2012: 15.5
|
||||
Yes Misra C 2012: 15.6
|
||||
Yes Misra C 2012: 15.7
|
||||
Yes Misra C 2012: 16.1
|
||||
Yes Misra C 2012: 16.2
|
||||
Yes Misra C 2012: 16.3
|
||||
Yes Misra C 2012: 16.4
|
||||
Yes Misra C 2012: 16.5
|
||||
Yes Misra C 2012: 16.6
|
||||
Yes Misra C 2012: 16.7
|
||||
Yes Misra C 2012: 17.1
|
||||
Yes Misra C 2012: 17.2
|
||||
Yes Misra C 2012: 17.3
|
||||
No Misra C 2012: 17.4
|
||||
Yes Misra C 2012: 17.5
|
||||
Yes Misra C 2012: 17.6
|
||||
Yes Misra C 2012: 17.7
|
||||
Yes Misra C 2012: 17.8
|
||||
No Misra C 2012: 17.9
|
||||
No Misra C 2012: 17.10
|
||||
No Misra C 2012: 17.11
|
||||
No Misra C 2012: 17.12
|
||||
No Misra C 2012: 17.13
|
||||
Yes Misra C 2012: 18.1
|
||||
Yes Misra C 2012: 18.2
|
||||
Yes Misra C 2012: 18.3
|
||||
Yes Misra C 2012: 18.4
|
||||
Yes Misra C 2012: 18.5
|
||||
Yes Misra C 2012: 18.6
|
||||
Yes Misra C 2012: 18.7
|
||||
Yes Misra C 2012: 18.8
|
||||
No Misra C 2012: 18.9
|
||||
No Misra C 2012: 18.10
|
||||
Yes Misra C 2012: 19.1
|
||||
Yes Misra C 2012: 19.2
|
||||
Yes Misra C 2012: 20.1
|
||||
Yes Misra C 2012: 20.2
|
||||
Yes Misra C 2012: 20.3
|
||||
Yes Misra C 2012: 20.4
|
||||
Yes Misra C 2012: 20.5
|
||||
Yes Misra C 2012: 20.6
|
||||
Yes Misra C 2012: 20.7
|
||||
Yes Misra C 2012: 20.8
|
||||
Yes Misra C 2012: 20.9
|
||||
Yes Misra C 2012: 20.10
|
||||
Yes Misra C 2012: 20.11
|
||||
Yes Misra C 2012: 20.12
|
||||
Yes Misra C 2012: 20.13
|
||||
Yes Misra C 2012: 20.14
|
||||
Yes Misra C 2012: 21.1
|
||||
Yes Misra C 2012: 21.2
|
||||
Yes Misra C 2012: 21.3
|
||||
Yes Misra C 2012: 21.4
|
||||
Yes Misra C 2012: 21.5
|
||||
Yes Misra C 2012: 21.6
|
||||
Yes Misra C 2012: 21.7
|
||||
Yes Misra C 2012: 21.8
|
||||
Yes Misra C 2012: 21.9
|
||||
Yes Misra C 2012: 21.10
|
||||
Yes Misra C 2012: 21.11
|
||||
Yes Misra C 2012: 21.12
|
||||
Yes Misra C 2012: 21.13 amendment:1
|
||||
Yes Misra C 2012: 21.14 amendment:1
|
||||
Yes Misra C 2012: 21.15 amendment:1
|
||||
Yes Misra C 2012: 21.16 amendment:1
|
||||
Yes Misra C 2012: 21.17 amendment:1
|
||||
Yes Misra C 2012: 21.18 amendment:1
|
||||
Yes Misra C 2012: 21.19 amendment:1
|
||||
Yes Misra C 2012: 21.20 amendment:1
|
||||
Yes Misra C 2012: 21.21 amendment:3
|
||||
No Misra C 2012: 21.22 amendment:3 require:premium
|
||||
No Misra C 2012: 21.23 amendment:3 require:premium
|
||||
No Misra C 2012: 21.24 amendment:3 require:premium
|
||||
No Misra C 2012: 21.25 amendment:4 require:premium
|
||||
No Misra C 2012: 21.26 amendment:4 require:premium
|
||||
Yes Misra C 2012: 22.1
|
||||
Yes Misra C 2012: 22.2
|
||||
Yes Misra C 2012: 22.3
|
||||
Yes Misra C 2012: 22.4
|
||||
Yes Misra C 2012: 22.5
|
||||
Yes Misra C 2012: 22.6
|
||||
Yes Misra C 2012: 22.7 amendment:1
|
||||
Yes Misra C 2012: 22.8 amendment:1
|
||||
Yes Misra C 2012: 22.9 amendment:1
|
||||
Yes Misra C 2012: 22.10 amendment:1
|
||||
No Misra C 2012: 22.11 amendment:4 require:premium
|
||||
No Misra C 2012: 22.12 amendment:4 require:premium
|
||||
No Misra C 2012: 22.13 amendment:4 require:premium
|
||||
No Misra C 2012: 22.14 amendment:4 require:premium
|
||||
No Misra C 2012: 22.15 amendment:4 require:premium
|
||||
No Misra C 2012: 22.16 amendment:4 require:premium
|
||||
No Misra C 2012: 22.17 amendment:4 require:premium
|
||||
No Misra C 2012: 22.18 amendment:4 require:premium
|
||||
No Misra C 2012: 22.19 amendment:4 require:premium
|
||||
No Misra C 2012: 22.20 amendment:4 require:premium
|
||||
No Misra C 2012: 23.1 amendment:3 require:premium
|
||||
No Misra C 2012: 23.2 amendment:3 require:premium
|
||||
No Misra C 2012: 23.3 amendment:3 require:premium
|
||||
No Misra C 2012: 23.4 amendment:3 require:premium
|
||||
No Misra C 2012: 23.5 amendment:3 require:premium
|
||||
No Misra C 2012: 23.6 amendment:3 require:premium
|
||||
No Misra C 2012: 23.7 amendment:3 require:premium
|
||||
No Misra C 2012: 23.8 amendment:3 require:premium
|
||||
|
||||
|
||||
Misra C++ 2008
|
||||
--------------
|
||||
Not available, Cppcheck Premium is not used
|
||||
|
||||
|
||||
Misra C++ 2023
|
||||
--------------
|
||||
Not available, Cppcheck Premium is not used
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
TEST variant options:
|
||||
--enable=all --disable=unusedFunction -DPANDA --addon=misra -DSTM32H7 -DSTM32H725xx /board/main.c
|
||||
|
||||
|
||||
Critical errors
|
||||
---------------
|
||||
No critical errors, all files were checked.
|
||||
Important: Analysis is still not guaranteed to be 'complete' it is possible there are false negatives.
|
||||
|
||||
|
||||
Open source checkers
|
||||
--------------------
|
||||
Yes Check64BitPortability::pointerassignment
|
||||
Yes CheckAssert::assertWithSideEffects
|
||||
Yes CheckAutoVariables::assignFunctionArg
|
||||
Yes CheckAutoVariables::autoVariables
|
||||
Yes CheckAutoVariables::checkVarLifetime
|
||||
No CheckBool::checkAssignBoolToFloat require:style,c++
|
||||
Yes CheckBool::checkAssignBoolToPointer
|
||||
No CheckBool::checkBitwiseOnBoolean require:style,inconclusive
|
||||
Yes CheckBool::checkComparisonOfBoolExpressionWithInt
|
||||
No CheckBool::checkComparisonOfBoolWithBool require:style,c++
|
||||
No CheckBool::checkComparisonOfBoolWithInt require:warning,c++
|
||||
No CheckBool::checkComparisonOfFuncReturningBool require:style,c++
|
||||
Yes CheckBool::checkIncrementBoolean
|
||||
Yes CheckBool::pointerArithBool
|
||||
Yes CheckBool::returnValueOfFunctionReturningBool
|
||||
No CheckBoost::checkBoostForeachModification
|
||||
Yes CheckBufferOverrun::analyseWholeProgram
|
||||
Yes CheckBufferOverrun::argumentSize
|
||||
Yes CheckBufferOverrun::arrayIndex
|
||||
Yes CheckBufferOverrun::arrayIndexThenCheck
|
||||
Yes CheckBufferOverrun::bufferOverflow
|
||||
Yes CheckBufferOverrun::negativeArraySize
|
||||
Yes CheckBufferOverrun::objectIndex
|
||||
Yes CheckBufferOverrun::pointerArithmetic
|
||||
No CheckBufferOverrun::stringNotZeroTerminated require:warning,inconclusive
|
||||
Yes CheckClass::analyseWholeProgram
|
||||
No CheckClass::checkConst require:style,inconclusive
|
||||
No CheckClass::checkConstructors require:style,warning
|
||||
No CheckClass::checkCopyConstructors require:warning
|
||||
No CheckClass::checkDuplInheritedMembers require:warning
|
||||
No CheckClass::checkExplicitConstructors require:style
|
||||
No CheckClass::checkMemset
|
||||
No CheckClass::checkMissingOverride require:style,c++03
|
||||
No CheckClass::checkReturnByReference require:performance
|
||||
No CheckClass::checkSelfInitialization
|
||||
No CheckClass::checkThisUseAfterFree require:warning
|
||||
No CheckClass::checkUnsafeClassRefMember require:warning,safeChecks
|
||||
No CheckClass::checkUselessOverride require:style
|
||||
No CheckClass::checkVirtualFunctionCallInConstructor require:warning
|
||||
No CheckClass::initializationListUsage require:performance
|
||||
No CheckClass::initializerListOrder require:style,inconclusive
|
||||
No CheckClass::operatorEqRetRefThis require:style
|
||||
No CheckClass::operatorEqToSelf require:warning
|
||||
No CheckClass::privateFunctions require:style
|
||||
No CheckClass::thisSubtraction require:warning
|
||||
No CheckClass::virtualDestructor
|
||||
Yes CheckCondition::alwaysTrueFalse
|
||||
Yes CheckCondition::assignIf
|
||||
Yes CheckCondition::checkAssignmentInCondition
|
||||
Yes CheckCondition::checkBadBitmaskCheck
|
||||
Yes CheckCondition::checkCompareValueOutOfTypeRange
|
||||
Yes CheckCondition::checkDuplicateConditionalAssign
|
||||
Yes CheckCondition::checkIncorrectLogicOperator
|
||||
Yes CheckCondition::checkInvalidTestForOverflow
|
||||
Yes CheckCondition::checkModuloAlwaysTrueFalse
|
||||
Yes CheckCondition::checkPointerAdditionResultNotNull
|
||||
Yes CheckCondition::clarifyCondition
|
||||
Yes CheckCondition::comparison
|
||||
Yes CheckCondition::duplicateCondition
|
||||
Yes CheckCondition::multiCondition
|
||||
Yes CheckCondition::multiCondition2
|
||||
No CheckExceptionSafety::checkCatchExceptionByValue require:style
|
||||
No CheckExceptionSafety::checkRethrowCopy require:style
|
||||
No CheckExceptionSafety::deallocThrow require:warning
|
||||
No CheckExceptionSafety::destructors require:warning
|
||||
No CheckExceptionSafety::nothrowThrows
|
||||
No CheckExceptionSafety::rethrowNoCurrentException
|
||||
No CheckExceptionSafety::unhandledExceptionSpecification require:style,inconclusive
|
||||
Yes CheckFunctions::checkIgnoredReturnValue
|
||||
Yes CheckFunctions::checkMathFunctions
|
||||
Yes CheckFunctions::checkMissingReturn
|
||||
Yes CheckFunctions::checkProhibitedFunctions
|
||||
Yes CheckFunctions::invalidFunctionUsage
|
||||
Yes CheckFunctions::memsetInvalid2ndParam
|
||||
Yes CheckFunctions::memsetZeroBytes
|
||||
No CheckFunctions::returnLocalStdMove require:performance,c++11
|
||||
Yes CheckFunctions::useStandardLibrary
|
||||
No CheckIO::checkCoutCerrMisusage require:c
|
||||
Yes CheckIO::checkFileUsage
|
||||
Yes CheckIO::checkWrongPrintfScanfArguments
|
||||
Yes CheckIO::invalidScanf
|
||||
Yes CheckLeakAutoVar::check
|
||||
No CheckMemoryLeakInClass::check
|
||||
Yes CheckMemoryLeakInFunction::checkReallocUsage
|
||||
Yes CheckMemoryLeakNoVar::check
|
||||
No CheckMemoryLeakNoVar::checkForUnsafeArgAlloc
|
||||
Yes CheckMemoryLeakStructMember::check
|
||||
Yes CheckNullPointer::analyseWholeProgram
|
||||
Yes CheckNullPointer::arithmetic
|
||||
Yes CheckNullPointer::nullConstantDereference
|
||||
Yes CheckNullPointer::nullPointer
|
||||
No CheckOther::checkAccessOfMovedVariable require:c++11,warning
|
||||
Yes CheckOther::checkCastIntToCharAndBack
|
||||
Yes CheckOther::checkCharVariable
|
||||
Yes CheckOther::checkComparePointers
|
||||
Yes CheckOther::checkComparisonFunctionIsAlwaysTrueOrFalse
|
||||
Yes CheckOther::checkConstPointer
|
||||
No CheckOther::checkConstVariable require:style,c++
|
||||
No CheckOther::checkDuplicateBranch require:style,inconclusive
|
||||
Yes CheckOther::checkDuplicateExpression
|
||||
Yes CheckOther::checkEvaluationOrder
|
||||
Yes CheckOther::checkFuncArgNamesDifferent
|
||||
No CheckOther::checkIncompleteArrayFill require:warning,portability,inconclusive
|
||||
Yes CheckOther::checkIncompleteStatement
|
||||
No CheckOther::checkInterlockedDecrement require:windows-platform
|
||||
Yes CheckOther::checkInvalidFree
|
||||
Yes CheckOther::checkKnownArgument
|
||||
Yes CheckOther::checkKnownPointerToBool
|
||||
No CheckOther::checkMisusedScopedObject require:style,c++
|
||||
Yes CheckOther::checkModuloOfOne
|
||||
Yes CheckOther::checkNanInArithmeticExpression
|
||||
Yes CheckOther::checkNegativeBitwiseShift
|
||||
Yes CheckOther::checkOverlappingWrite
|
||||
No CheckOther::checkPassByReference require:performance,c++
|
||||
Yes CheckOther::checkRedundantAssignment
|
||||
No CheckOther::checkRedundantCopy require:c++,performance,inconclusive
|
||||
Yes CheckOther::checkRedundantPointerOp
|
||||
Yes CheckOther::checkShadowVariables
|
||||
Yes CheckOther::checkSignOfUnsignedVariable
|
||||
No CheckOther::checkSuspiciousCaseInSwitch require:warning,inconclusive
|
||||
No CheckOther::checkSuspiciousSemicolon require:warning,inconclusive
|
||||
Yes CheckOther::checkUnreachableCode
|
||||
Yes CheckOther::checkUnusedLabel
|
||||
Yes CheckOther::checkVarFuncNullUB
|
||||
Yes CheckOther::checkVariableScope
|
||||
Yes CheckOther::checkZeroDivision
|
||||
Yes CheckOther::clarifyCalculation
|
||||
Yes CheckOther::clarifyStatement
|
||||
Yes CheckOther::invalidPointerCast
|
||||
Yes CheckOther::redundantBitwiseOperationInSwitch
|
||||
No CheckOther::warningOldStylePointerCast require:style,c++
|
||||
No CheckPostfixOperator::postfixOperator require:performance
|
||||
Yes CheckSizeof::checkSizeofForArrayParameter
|
||||
Yes CheckSizeof::checkSizeofForNumericParameter
|
||||
Yes CheckSizeof::checkSizeofForPointerSize
|
||||
Yes CheckSizeof::sizeofCalculation
|
||||
Yes CheckSizeof::sizeofFunction
|
||||
Yes CheckSizeof::sizeofVoid
|
||||
Yes CheckSizeof::sizeofsizeof
|
||||
No CheckSizeof::suspiciousSizeofCalculation require:warning,inconclusive
|
||||
No CheckStl::checkDereferenceInvalidIterator require:warning
|
||||
No CheckStl::checkDereferenceInvalidIterator2
|
||||
No CheckStl::checkFindInsert require:performance
|
||||
No CheckStl::checkMutexes require:warning
|
||||
No CheckStl::erase
|
||||
No CheckStl::eraseIteratorOutOfBounds
|
||||
No CheckStl::if_find require:warning,performance
|
||||
No CheckStl::invalidContainer
|
||||
No CheckStl::iterators
|
||||
No CheckStl::knownEmptyContainer require:style
|
||||
No CheckStl::misMatchingContainerIterator
|
||||
No CheckStl::misMatchingContainers
|
||||
No CheckStl::missingComparison require:warning
|
||||
No CheckStl::negativeIndex
|
||||
No CheckStl::outOfBounds
|
||||
No CheckStl::outOfBoundsIndexExpression
|
||||
No CheckStl::redundantCondition require:style
|
||||
No CheckStl::size require:performance,c++03
|
||||
No CheckStl::stlBoundaries
|
||||
No CheckStl::stlOutOfBounds
|
||||
No CheckStl::string_c_str
|
||||
No CheckStl::useStlAlgorithm require:style
|
||||
No CheckStl::uselessCalls require:performance,warning
|
||||
Yes CheckString::checkAlwaysTrueOrFalseStringCompare
|
||||
Yes CheckString::checkIncorrectStringCompare
|
||||
Yes CheckString::checkSuspiciousStringCompare
|
||||
Yes CheckString::overlappingStrcmp
|
||||
Yes CheckString::sprintfOverlappingData
|
||||
Yes CheckString::strPlusChar
|
||||
Yes CheckString::stringLiteralWrite
|
||||
Yes CheckType::checkFloatToIntegerOverflow
|
||||
Yes CheckType::checkIntegerOverflow
|
||||
Yes CheckType::checkLongCast
|
||||
Yes CheckType::checkSignConversion
|
||||
Yes CheckType::checkTooBigBitwiseShift
|
||||
Yes CheckUninitVar::check
|
||||
Yes CheckUninitVar::valueFlowUninit
|
||||
No CheckUnusedFunctions::check require:unusedFunction
|
||||
Yes CheckUnusedVar::checkFunctionVariableUsage
|
||||
Yes CheckUnusedVar::checkStructMemberUsage
|
||||
Yes CheckVaarg::va_list_usage
|
||||
Yes CheckVaarg::va_start_argument
|
||||
|
||||
|
||||
Premium checkers
|
||||
----------------
|
||||
Not available, Cppcheck Premium is not used
|
||||
|
||||
|
||||
Autosar
|
||||
-------
|
||||
Not available, Cppcheck Premium is not used
|
||||
|
||||
|
||||
Cert C
|
||||
------
|
||||
Not available, Cppcheck Premium is not used
|
||||
|
||||
|
||||
Cert C++
|
||||
--------
|
||||
Not available, Cppcheck Premium is not used
|
||||
|
||||
|
||||
Misra C 2012
|
||||
------------
|
||||
Yes Misra C 2012: 1.1
|
||||
Yes Misra C 2012: 1.2
|
||||
Yes Misra C 2012: 1.3
|
||||
Yes Misra C 2012: 1.4 amendment:2
|
||||
No Misra C 2012: 1.5 amendment:3 require:premium
|
||||
Yes Misra C 2012: 2.1
|
||||
Yes Misra C 2012: 2.2
|
||||
Yes Misra C 2012: 2.3
|
||||
Yes Misra C 2012: 2.4
|
||||
Yes Misra C 2012: 2.5
|
||||
Yes Misra C 2012: 2.6
|
||||
Yes Misra C 2012: 2.7
|
||||
Yes Misra C 2012: 2.8
|
||||
Yes Misra C 2012: 3.1
|
||||
Yes Misra C 2012: 3.2
|
||||
Yes Misra C 2012: 4.1
|
||||
Yes Misra C 2012: 4.2
|
||||
Yes Misra C 2012: 5.1
|
||||
Yes Misra C 2012: 5.2
|
||||
Yes Misra C 2012: 5.3
|
||||
Yes Misra C 2012: 5.4
|
||||
Yes Misra C 2012: 5.5
|
||||
Yes Misra C 2012: 5.6
|
||||
Yes Misra C 2012: 5.7
|
||||
Yes Misra C 2012: 5.8
|
||||
Yes Misra C 2012: 5.9
|
||||
Yes Misra C 2012: 6.1
|
||||
Yes Misra C 2012: 6.2
|
||||
No Misra C 2012: 6.3
|
||||
Yes Misra C 2012: 7.1
|
||||
Yes Misra C 2012: 7.2
|
||||
Yes Misra C 2012: 7.3
|
||||
Yes Misra C 2012: 7.4
|
||||
No Misra C 2012: 7.5
|
||||
No Misra C 2012: 7.6
|
||||
Yes Misra C 2012: 8.1
|
||||
Yes Misra C 2012: 8.2
|
||||
No Misra C 2012: 8.3
|
||||
Yes Misra C 2012: 8.4
|
||||
Yes Misra C 2012: 8.5
|
||||
Yes Misra C 2012: 8.6
|
||||
Yes Misra C 2012: 8.7
|
||||
Yes Misra C 2012: 8.8
|
||||
Yes Misra C 2012: 8.9
|
||||
Yes Misra C 2012: 8.10
|
||||
Yes Misra C 2012: 8.11
|
||||
Yes Misra C 2012: 8.12
|
||||
Yes Misra C 2012: 8.13
|
||||
Yes Misra C 2012: 8.14
|
||||
No Misra C 2012: 8.15
|
||||
No Misra C 2012: 8.16
|
||||
No Misra C 2012: 8.17
|
||||
Yes Misra C 2012: 9.1
|
||||
Yes Misra C 2012: 9.2
|
||||
Yes Misra C 2012: 9.3
|
||||
Yes Misra C 2012: 9.4
|
||||
Yes Misra C 2012: 9.5
|
||||
No Misra C 2012: 9.6
|
||||
No Misra C 2012: 9.7
|
||||
Yes Misra C 2012: 10.1
|
||||
Yes Misra C 2012: 10.2
|
||||
Yes Misra C 2012: 10.3
|
||||
Yes Misra C 2012: 10.4
|
||||
Yes Misra C 2012: 10.5
|
||||
Yes Misra C 2012: 10.6
|
||||
Yes Misra C 2012: 10.7
|
||||
Yes Misra C 2012: 10.8
|
||||
Yes Misra C 2012: 11.1
|
||||
Yes Misra C 2012: 11.2
|
||||
Yes Misra C 2012: 11.3
|
||||
Yes Misra C 2012: 11.4
|
||||
Yes Misra C 2012: 11.5
|
||||
Yes Misra C 2012: 11.6
|
||||
Yes Misra C 2012: 11.7
|
||||
Yes Misra C 2012: 11.8
|
||||
Yes Misra C 2012: 11.9
|
||||
No Misra C 2012: 11.10
|
||||
Yes Misra C 2012: 12.1
|
||||
Yes Misra C 2012: 12.2
|
||||
Yes Misra C 2012: 12.3
|
||||
Yes Misra C 2012: 12.4
|
||||
Yes Misra C 2012: 12.5 amendment:1
|
||||
No Misra C 2012: 12.6 amendment:4 require:premium
|
||||
Yes Misra C 2012: 13.1
|
||||
No Misra C 2012: 13.2
|
||||
Yes Misra C 2012: 13.3
|
||||
Yes Misra C 2012: 13.4
|
||||
Yes Misra C 2012: 13.5
|
||||
Yes Misra C 2012: 13.6
|
||||
Yes Misra C 2012: 14.1
|
||||
Yes Misra C 2012: 14.2
|
||||
Yes Misra C 2012: 14.3
|
||||
Yes Misra C 2012: 14.4
|
||||
Yes Misra C 2012: 15.1
|
||||
Yes Misra C 2012: 15.2
|
||||
Yes Misra C 2012: 15.3
|
||||
Yes Misra C 2012: 15.4
|
||||
Yes Misra C 2012: 15.5
|
||||
Yes Misra C 2012: 15.6
|
||||
Yes Misra C 2012: 15.7
|
||||
Yes Misra C 2012: 16.1
|
||||
Yes Misra C 2012: 16.2
|
||||
Yes Misra C 2012: 16.3
|
||||
Yes Misra C 2012: 16.4
|
||||
Yes Misra C 2012: 16.5
|
||||
Yes Misra C 2012: 16.6
|
||||
Yes Misra C 2012: 16.7
|
||||
Yes Misra C 2012: 17.1
|
||||
Yes Misra C 2012: 17.2
|
||||
Yes Misra C 2012: 17.3
|
||||
No Misra C 2012: 17.4
|
||||
Yes Misra C 2012: 17.5
|
||||
Yes Misra C 2012: 17.6
|
||||
Yes Misra C 2012: 17.7
|
||||
Yes Misra C 2012: 17.8
|
||||
No Misra C 2012: 17.9
|
||||
No Misra C 2012: 17.10
|
||||
No Misra C 2012: 17.11
|
||||
No Misra C 2012: 17.12
|
||||
No Misra C 2012: 17.13
|
||||
Yes Misra C 2012: 18.1
|
||||
Yes Misra C 2012: 18.2
|
||||
Yes Misra C 2012: 18.3
|
||||
Yes Misra C 2012: 18.4
|
||||
Yes Misra C 2012: 18.5
|
||||
Yes Misra C 2012: 18.6
|
||||
Yes Misra C 2012: 18.7
|
||||
Yes Misra C 2012: 18.8
|
||||
No Misra C 2012: 18.9
|
||||
No Misra C 2012: 18.10
|
||||
Yes Misra C 2012: 19.1
|
||||
Yes Misra C 2012: 19.2
|
||||
Yes Misra C 2012: 20.1
|
||||
Yes Misra C 2012: 20.2
|
||||
Yes Misra C 2012: 20.3
|
||||
Yes Misra C 2012: 20.4
|
||||
Yes Misra C 2012: 20.5
|
||||
Yes Misra C 2012: 20.6
|
||||
Yes Misra C 2012: 20.7
|
||||
Yes Misra C 2012: 20.8
|
||||
Yes Misra C 2012: 20.9
|
||||
Yes Misra C 2012: 20.10
|
||||
Yes Misra C 2012: 20.11
|
||||
Yes Misra C 2012: 20.12
|
||||
Yes Misra C 2012: 20.13
|
||||
Yes Misra C 2012: 20.14
|
||||
Yes Misra C 2012: 21.1
|
||||
Yes Misra C 2012: 21.2
|
||||
Yes Misra C 2012: 21.3
|
||||
Yes Misra C 2012: 21.4
|
||||
Yes Misra C 2012: 21.5
|
||||
Yes Misra C 2012: 21.6
|
||||
Yes Misra C 2012: 21.7
|
||||
Yes Misra C 2012: 21.8
|
||||
Yes Misra C 2012: 21.9
|
||||
Yes Misra C 2012: 21.10
|
||||
Yes Misra C 2012: 21.11
|
||||
Yes Misra C 2012: 21.12
|
||||
Yes Misra C 2012: 21.13 amendment:1
|
||||
Yes Misra C 2012: 21.14 amendment:1
|
||||
Yes Misra C 2012: 21.15 amendment:1
|
||||
Yes Misra C 2012: 21.16 amendment:1
|
||||
Yes Misra C 2012: 21.17 amendment:1
|
||||
Yes Misra C 2012: 21.18 amendment:1
|
||||
Yes Misra C 2012: 21.19 amendment:1
|
||||
Yes Misra C 2012: 21.20 amendment:1
|
||||
Yes Misra C 2012: 21.21 amendment:3
|
||||
No Misra C 2012: 21.22 amendment:3 require:premium
|
||||
No Misra C 2012: 21.23 amendment:3 require:premium
|
||||
No Misra C 2012: 21.24 amendment:3 require:premium
|
||||
No Misra C 2012: 21.25 amendment:4 require:premium
|
||||
No Misra C 2012: 21.26 amendment:4 require:premium
|
||||
Yes Misra C 2012: 22.1
|
||||
Yes Misra C 2012: 22.2
|
||||
Yes Misra C 2012: 22.3
|
||||
Yes Misra C 2012: 22.4
|
||||
Yes Misra C 2012: 22.5
|
||||
Yes Misra C 2012: 22.6
|
||||
Yes Misra C 2012: 22.7 amendment:1
|
||||
Yes Misra C 2012: 22.8 amendment:1
|
||||
Yes Misra C 2012: 22.9 amendment:1
|
||||
Yes Misra C 2012: 22.10 amendment:1
|
||||
No Misra C 2012: 22.11 amendment:4 require:premium
|
||||
No Misra C 2012: 22.12 amendment:4 require:premium
|
||||
No Misra C 2012: 22.13 amendment:4 require:premium
|
||||
No Misra C 2012: 22.14 amendment:4 require:premium
|
||||
No Misra C 2012: 22.15 amendment:4 require:premium
|
||||
No Misra C 2012: 22.16 amendment:4 require:premium
|
||||
No Misra C 2012: 22.17 amendment:4 require:premium
|
||||
No Misra C 2012: 22.18 amendment:4 require:premium
|
||||
No Misra C 2012: 22.19 amendment:4 require:premium
|
||||
No Misra C 2012: 22.20 amendment:4 require:premium
|
||||
No Misra C 2012: 23.1 amendment:3 require:premium
|
||||
No Misra C 2012: 23.2 amendment:3 require:premium
|
||||
No Misra C 2012: 23.3 amendment:3 require:premium
|
||||
No Misra C 2012: 23.4 amendment:3 require:premium
|
||||
No Misra C 2012: 23.5 amendment:3 require:premium
|
||||
No Misra C 2012: 23.6 amendment:3 require:premium
|
||||
No Misra C 2012: 23.7 amendment:3 require:premium
|
||||
No Misra C 2012: 23.8 amendment:3 require:premium
|
||||
|
||||
|
||||
Misra C++ 2008
|
||||
--------------
|
||||
Not available, Cppcheck Premium is not used
|
||||
|
||||
|
||||
Misra C++ 2023
|
||||
--------------
|
||||
Not available, Cppcheck Premium is not used
|
||||
@@ -0,0 +1,156 @@
|
||||
1.1
|
||||
1.2 X (Addon)
|
||||
1.3 X (Cppcheck)
|
||||
2.1 X (Cppcheck)
|
||||
2.2 X (Addon)
|
||||
2.3 X (Addon)
|
||||
2.4 X (Addon)
|
||||
2.5 X (Addon)
|
||||
2.6 X (Cppcheck)
|
||||
2.7 X (Addon)
|
||||
3.1 X (Addon)
|
||||
3.2 X (Addon)
|
||||
4.1 X (Addon)
|
||||
4.2 X (Addon)
|
||||
5.1 X (Addon)
|
||||
5.2 X (Addon)
|
||||
5.3 X (Cppcheck)
|
||||
5.4 X (Addon)
|
||||
5.5 X (Addon)
|
||||
5.6 X (Addon)
|
||||
5.7 X (Addon)
|
||||
5.8 X (Addon)
|
||||
5.9 X (Addon)
|
||||
6.1 X (Addon)
|
||||
6.2 X (Addon)
|
||||
7.1 X (Addon)
|
||||
7.2 X (Addon)
|
||||
7.3 X (Addon)
|
||||
7.4 X (Addon)
|
||||
8.1 X (Addon)
|
||||
8.2 X (Addon)
|
||||
8.3 X (Cppcheck)
|
||||
8.4 X (Addon)
|
||||
8.5 X (Addon)
|
||||
8.6 X (Addon)
|
||||
8.7 X (Addon)
|
||||
8.8 X (Addon)
|
||||
8.9 X (Addon)
|
||||
8.10 X (Addon)
|
||||
8.11 X (Addon)
|
||||
8.12 X (Addon)
|
||||
8.13 X (Cppcheck)
|
||||
8.14 X (Addon)
|
||||
9.1 X (Cppcheck)
|
||||
9.2 X (Addon)
|
||||
9.3 X (Addon)
|
||||
9.4 X (Addon)
|
||||
9.5 X (Addon)
|
||||
10.1 X (Addon)
|
||||
10.2 X (Addon)
|
||||
10.3 X (Addon)
|
||||
10.4 X (Addon)
|
||||
10.5 X (Addon)
|
||||
10.6 X (Addon)
|
||||
10.7 X (Addon)
|
||||
10.8 X (Addon)
|
||||
11.1 X (Addon)
|
||||
11.2 X (Addon)
|
||||
11.3 X (Addon)
|
||||
11.4 X (Addon)
|
||||
11.5 X (Addon)
|
||||
11.6 X (Addon)
|
||||
11.7 X (Addon)
|
||||
11.8 X (Addon)
|
||||
11.9 X (Addon)
|
||||
12.1 X (Addon)
|
||||
12.2 X (Addon)
|
||||
12.3 X (Addon)
|
||||
12.4 X (Addon)
|
||||
13.1 X (Addon)
|
||||
13.2 X (Cppcheck)
|
||||
13.3 X (Addon)
|
||||
13.4 X (Addon)
|
||||
13.5 X (Addon)
|
||||
13.6 X (Addon)
|
||||
14.1 X (Addon)
|
||||
14.2 X (Addon)
|
||||
14.3 X (Cppcheck)
|
||||
14.4 X (Addon)
|
||||
15.1 X (Addon)
|
||||
15.2 X (Addon)
|
||||
15.3 X (Addon)
|
||||
15.4 X (Addon)
|
||||
15.5 X (Addon)
|
||||
15.6 X (Addon)
|
||||
15.7 X (Addon)
|
||||
16.1 X (Addon)
|
||||
16.2 X (Addon)
|
||||
16.3 X (Addon)
|
||||
16.4 X (Addon)
|
||||
16.5 X (Addon)
|
||||
16.6 X (Addon)
|
||||
16.7 X (Addon)
|
||||
17.1 X (Addon)
|
||||
17.2 X (Addon)
|
||||
17.3 X (Addon)
|
||||
17.4 X (Cppcheck)
|
||||
17.5 X (Cppcheck)
|
||||
17.6 X (Addon)
|
||||
17.7 X (Addon)
|
||||
17.8 X (Addon)
|
||||
18.1 X (Cppcheck)
|
||||
18.2 X (Cppcheck)
|
||||
18.3 X (Cppcheck)
|
||||
18.4 X (Addon)
|
||||
18.5 X (Addon)
|
||||
18.6 X (Cppcheck)
|
||||
18.7 X (Addon)
|
||||
18.8 X (Addon)
|
||||
19.1 X (Cppcheck)
|
||||
19.2 X (Addon)
|
||||
20.1 X (Addon)
|
||||
20.2 X (Addon)
|
||||
20.3 X (Addon)
|
||||
20.4 X (Addon)
|
||||
20.5 X (Addon)
|
||||
20.6 X (Cppcheck)
|
||||
20.7 X (Addon)
|
||||
20.8 X (Addon)
|
||||
20.9 X (Addon)
|
||||
20.10 X (Addon)
|
||||
20.11 X (Addon)
|
||||
20.12 X (Addon)
|
||||
20.13 X (Addon)
|
||||
20.14 X (Addon)
|
||||
21.1 X (Addon)
|
||||
21.2 X (Addon)
|
||||
21.3 X (Addon)
|
||||
21.4 X (Addon)
|
||||
21.5 X (Addon)
|
||||
21.6 X (Addon)
|
||||
21.7 X (Addon)
|
||||
21.8 X (Addon)
|
||||
21.9 X (Addon)
|
||||
21.10 X (Addon)
|
||||
21.11 X (Addon)
|
||||
21.12 X (Addon)
|
||||
21.13 X (Cppcheck)
|
||||
21.14 X (Addon)
|
||||
21.15 X (Addon)
|
||||
21.16 X (Addon)
|
||||
21.17 X (Cppcheck)
|
||||
21.18 X (Cppcheck)
|
||||
21.19 X (Addon)
|
||||
21.20 X (Addon)
|
||||
21.21 X (Addon)
|
||||
22.1 X (Cppcheck)
|
||||
22.2 X (Cppcheck)
|
||||
22.3 X (Cppcheck)
|
||||
22.4 X (Cppcheck)
|
||||
22.5 X (Addon)
|
||||
22.6 X (Cppcheck)
|
||||
22.7 X (Addon)
|
||||
22.8 X (Addon)
|
||||
22.9 X (Addon)
|
||||
22.10 X (Addon)
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
: "${CPPCHECK_DIR:=$DIR/cppcheck/}"
|
||||
|
||||
if [ ! -d "$CPPCHECK_DIR" ]; then
|
||||
git clone https://github.com/danmar/cppcheck.git $CPPCHECK_DIR
|
||||
fi
|
||||
|
||||
cd $CPPCHECK_DIR
|
||||
|
||||
VERS="2.14.1"
|
||||
git fetch --all --tags --force
|
||||
git checkout $VERS
|
||||
|
||||
#make clean
|
||||
make MATCHCOMPILTER=yes CXXFLAGS="-O2" -j8
|
||||
@@ -0,0 +1,27 @@
|
||||
# Advisory: casting from void pointer to type pointer is ok. Done by STM libraries as well
|
||||
misra-c2012-11.4
|
||||
# Advisory: casting from void pointer to type pointer is ok. Done by STM libraries as well
|
||||
misra-c2012-11.5
|
||||
# Advisory: as stated in the Misra document, use of goto statements in accordance to 15.2 and 15.3 is ok
|
||||
misra-c2012-15.1
|
||||
# Advisory: union types can be used
|
||||
misra-c2012-19.2
|
||||
# Advisory: The # and ## preprocessor operators should not be used
|
||||
misra-c2012-20.10
|
||||
|
||||
# needed since not all of these suppressions are applicable to all builds
|
||||
unmatchedSuppression
|
||||
|
||||
# All interrupt handlers are defined, including ones we don't use
|
||||
unusedFunction:*/interrupt_handlers*.h
|
||||
|
||||
# all of the below suppressions are from new checks introduced after updating
|
||||
# cppcheck from 2.5 -> 2.13. they are listed here to separate the update from
|
||||
# fixing the violations and all are intended to be removed soon after
|
||||
misra-c2012-2.5 # unused macros. a few legit, rest aren't common between F4/H7 builds. should we do this in the unusedFunction pass?
|
||||
misra-c2012-8.7
|
||||
misra-c2012-8.4
|
||||
misra-c2012-21.15
|
||||
|
||||
# FIXME: violations are in ST's F4 headers
|
||||
misra-c2012-12.2
|
||||
Executable
+85
@@ -0,0 +1,85 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
PANDA_DIR=$(realpath $DIR/../../)
|
||||
|
||||
GREEN="\e[1;32m"
|
||||
YELLOW="\e[1;33m"
|
||||
RED="\e[1;31m"
|
||||
NC='\033[0m'
|
||||
|
||||
: "${CPPCHECK_DIR:=$DIR/cppcheck/}"
|
||||
|
||||
# install cppcheck if missing
|
||||
if [ -z "${SKIP_CPPCHECK_INSTALL}" ]; then
|
||||
$DIR/install.sh
|
||||
fi
|
||||
|
||||
# ensure checked in coverage table is up to date
|
||||
cd $DIR
|
||||
if [ -z "$SKIP_TABLES_DIFF" ]; then
|
||||
python $CPPCHECK_DIR/addons/misra.py -generate-table > coverage_table
|
||||
if ! git diff --quiet coverage_table; then
|
||||
echo -e "${YELLOW}MISRA coverage table doesn't match. Update and commit:${NC}"
|
||||
exit 3
|
||||
fi
|
||||
fi
|
||||
|
||||
cd $PANDA_DIR
|
||||
if [ -z "${SKIP_BUILD}" ]; then
|
||||
scons -j8
|
||||
fi
|
||||
|
||||
CHECKLIST=$DIR/checkers.txt
|
||||
echo "Cppcheck checkers list from test_misra.sh:" > $CHECKLIST
|
||||
|
||||
cppcheck() {
|
||||
# get all gcc defines: arm-none-eabi-gcc -dM -E - < /dev/null
|
||||
COMMON_DEFINES="-D__GNUC__=9 -UCMSIS_NVIC_VIRTUAL -UCMSIS_VECTAB_VIRTUAL"
|
||||
|
||||
# note that cppcheck build cache results in inconsistent results as of v2.13.0
|
||||
OUTPUT=$DIR/.output.log
|
||||
|
||||
echo -e "\n\n\n\n\nTEST variant options:" >> $CHECKLIST
|
||||
echo -e ""${@//$PANDA_DIR/}"\n\n" >> $CHECKLIST # (absolute path removed)
|
||||
|
||||
$CPPCHECK_DIR/cppcheck --inline-suppr -I $PANDA_DIR/board/ \
|
||||
-I "$(arm-none-eabi-gcc -print-file-name=include)" \
|
||||
-I $PANDA_DIR/board/stm32f4/inc/ -I $PANDA_DIR/board/stm32h7/inc/ \
|
||||
--suppressions-list=$DIR/suppressions.txt --suppress=*:*inc/* \
|
||||
--suppress=*:*include/* --error-exitcode=2 --check-level=exhaustive \
|
||||
--platform=arm32-wchar_t4 $COMMON_DEFINES --checkers-report=$CHECKLIST.tmp \
|
||||
--std=c11 "$@" |& tee $OUTPUT
|
||||
|
||||
cat $CHECKLIST.tmp >> $CHECKLIST
|
||||
rm $CHECKLIST.tmp
|
||||
# cppcheck bug: some MISRA errors won't result in the error exit code,
|
||||
# so check the output (https://trac.cppcheck.net/ticket/12440#no1)
|
||||
if grep -e "misra violation" -e "error" -e "style: " $OUTPUT > /dev/null; then
|
||||
printf "${RED}** FAILED: MISRA violations found!${NC}\n"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
PANDA_OPTS="--enable=all --disable=unusedFunction -DPANDA --addon=misra"
|
||||
|
||||
printf "\n${GREEN}** PANDA F4 CODE **${NC}\n"
|
||||
cppcheck $PANDA_OPTS -DSTM32F4 -DSTM32F413xx $PANDA_DIR/board/main.c
|
||||
|
||||
printf "\n${GREEN}** PANDA H7 CODE **${NC}\n"
|
||||
cppcheck $PANDA_OPTS -DSTM32H7 -DSTM32H725xx $PANDA_DIR/board/main.c
|
||||
|
||||
# unused needs to run globally
|
||||
#printf "\n${GREEN}** UNUSED ALL CODE **${NC}\n"
|
||||
#cppcheck --enable=unusedFunction --quiet $PANDA_DIR/board/
|
||||
|
||||
printf "\n${GREEN}Success!${NC} took $SECONDS seconds\n"
|
||||
|
||||
|
||||
# ensure list of checkers is up to date
|
||||
cd $DIR
|
||||
if [ -z "$SKIP_TABLES_DIFF" ] && ! git diff --quiet $CHECKLIST; then
|
||||
echo -e "\n${YELLOW}WARNING: Cppcheck checkers.txt report has changed. Review and commit...${NC}"
|
||||
exit 4
|
||||
fi
|
||||
Executable
+82
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import glob
|
||||
import pytest
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import random
|
||||
|
||||
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||
ROOT = os.path.join(HERE, "../../")
|
||||
|
||||
IGNORED_PATHS = (
|
||||
'board/obj',
|
||||
'board/jungle',
|
||||
'board/stm32h7/inc',
|
||||
'board/stm32f4/inc',
|
||||
'board/fake_stm.h',
|
||||
|
||||
# bootstub only files
|
||||
'board/flasher.h',
|
||||
'board/bootstub.c',
|
||||
'board/bootstub_declarations.h',
|
||||
'board/stm32h7/llflash.h',
|
||||
'board/stm32f4/llflash.h',
|
||||
)
|
||||
|
||||
mutations = [
|
||||
# default
|
||||
(None, None, False),
|
||||
# F4 only
|
||||
("board/stm32f4/llbxcan.h", "s/1U/1/g", True),
|
||||
# H7 only
|
||||
("board/stm32h7/llfdcan.h", "s/return ret;/if (true) { return ret; } else { return false; }/g", True),
|
||||
# general safety
|
||||
("board/safety/safety_toyota.h", "s/is_lkas_msg =.*;/is_lkas_msg = addr == 1 || addr == 2;/g", True),
|
||||
]
|
||||
|
||||
patterns = [
|
||||
# misra-c2012-13.3
|
||||
"$a void test(int tmp) { int tmp2 = tmp++ + 2; if (tmp2) {;}}",
|
||||
# misra-c2012-13.4
|
||||
"$a int test(int x, int y) { return (x=2) && (y=2); }",
|
||||
# misra-c2012-13.5
|
||||
"$a void test(int tmp) { if (true && tmp++) {;} }",
|
||||
# misra-c2012-13.6
|
||||
"$a void test(int tmp) { if (sizeof(tmp++)) {;} }",
|
||||
# misra-c2012-14.1
|
||||
"$a void test(float len) { for (float j = 0; j < len; j++) {;} }",
|
||||
# misra-c2012-14.4
|
||||
"$a void test(int len) { if (len - 8) {;} }",
|
||||
# misra-c2012-16.4
|
||||
r"$a void test(int temp) {switch (temp) { case 1: ; }}\n",
|
||||
# misra-c2012-17.8
|
||||
"$a void test(int cnt) { for (cnt=0;;cnt++) {;} }",
|
||||
# misra-c2012-20.4
|
||||
r"$a #define auto 1\n",
|
||||
# misra-c2012-20.5
|
||||
r"$a #define TEST 1\n#undef TEST\n",
|
||||
]
|
||||
|
||||
all_files = glob.glob('board/**', root_dir=ROOT, recursive=True)
|
||||
files = [f for f in all_files if f.endswith(('.c', '.h')) and not f.startswith(IGNORED_PATHS)]
|
||||
assert len(files) > 70, all(d in files for d in ('board/main.c', 'board/stm32f4/llbxcan.h', 'board/stm32h7/llfdcan.h', 'board/safety/safety_toyota.h'))
|
||||
|
||||
for p in patterns:
|
||||
mutations.append((random.choice(files), p, True))
|
||||
|
||||
@pytest.mark.parametrize("fn, patch, should_fail", mutations)
|
||||
def test_misra_mutation(fn, patch, should_fail):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
shutil.copytree(ROOT, tmp, dirs_exist_ok=True)
|
||||
|
||||
# apply patch
|
||||
if fn is not None:
|
||||
r = os.system(f"cd {tmp} && sed -i '{patch}' {fn}")
|
||||
assert r == 0
|
||||
|
||||
# run test
|
||||
r = subprocess.run("SKIP_TABLES_DIFF=1 tests/misra/test_misra.sh", cwd=tmp, shell=True)
|
||||
failed = r.returncode != 0
|
||||
assert failed == should_fail
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python3
|
||||
from panda import Panda, PandaDFU
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
HARDWARE.recover_internal_panda()
|
||||
Panda.wait_for_dfu(None, 5)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
p = PandaDFU(None)
|
||||
cfg = p.get_mcu_type().config
|
||||
|
||||
def readmem(addr, length, fn):
|
||||
print(f"reading {hex(addr)} {hex(length)} bytes to {fn}")
|
||||
max_size = 255
|
||||
with open(fn, "wb") as f:
|
||||
to_read = length
|
||||
while to_read > 0:
|
||||
l = min(to_read, max_size)
|
||||
dat = p._handle.read(addr, l)
|
||||
assert len(dat) == l
|
||||
f.write(dat)
|
||||
|
||||
to_read -= len(dat)
|
||||
addr += len(dat)
|
||||
|
||||
addr = cfg.bootstub_address
|
||||
for i, sector_size in enumerate(cfg.sector_sizes):
|
||||
readmem(addr, sector_size, f"sector_{i}.bin")
|
||||
addr += sector_size
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
rm -f /tmp/dump_bootstub
|
||||
rm -f /tmp/dump_main
|
||||
dfu-util -a 0 -s 0x08000000 -U /tmp/dump_bootstub
|
||||
dfu-util -a 0 -s 0x08004000 -U /tmp/dump_main
|
||||
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
# type: ignore
|
||||
from panda import Panda
|
||||
from hexdump import hexdump
|
||||
|
||||
DEBUG = False
|
||||
|
||||
if __name__ == "__main__":
|
||||
p = Panda()
|
||||
|
||||
length = p._handle.controlRead(Panda.REQUEST_IN, 0x06, 3 << 8 | 238, 0, 1)
|
||||
print('Microsoft OS String Descriptor')
|
||||
dat = p._handle.controlRead(Panda.REQUEST_IN, 0x06, 3 << 8 | 238, 0, length[0])
|
||||
if DEBUG:
|
||||
print(f'LEN: {hex(length[0])}')
|
||||
hexdump("".join(map(chr, dat)))
|
||||
|
||||
ms_vendor_code = dat[16]
|
||||
if DEBUG:
|
||||
print(f'MS_VENDOR_CODE: {hex(length[0])}')
|
||||
|
||||
print('\nMicrosoft Compatible ID Feature Descriptor')
|
||||
length = p._handle.controlRead(Panda.REQUEST_IN, ms_vendor_code, 0, 4, 1)
|
||||
if DEBUG:
|
||||
print(f'LEN: {hex(length[0])}')
|
||||
dat = p._handle.controlRead(Panda.REQUEST_IN, ms_vendor_code, 0, 4, length[0])
|
||||
hexdump("".join(map(chr, dat)))
|
||||
|
||||
print('\nMicrosoft Extended Properties Feature Descriptor')
|
||||
length = p._handle.controlRead(Panda.REQUEST_IN, ms_vendor_code, 0, 5, 1)
|
||||
if DEBUG:
|
||||
print(f'LEN: {hex(length[0])}')
|
||||
dat = p._handle.controlRead(Panda.REQUEST_IN, ms_vendor_code, 0, 5, length[0])
|
||||
hexdump("".join(map(chr, dat)))
|
||||
Executable
+49
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
import time
|
||||
from panda import Panda, PandaDFU
|
||||
|
||||
class GPIO:
|
||||
STM_RST_N = 124
|
||||
STM_BOOT0 = 134
|
||||
HUB_RST_N = 30
|
||||
|
||||
|
||||
def gpio_init(pin, output):
|
||||
with open(f"/sys/class/gpio/gpio{pin}/direction", 'wb') as f:
|
||||
f.write(b"out" if output else b"in")
|
||||
|
||||
def gpio_set(pin, high):
|
||||
with open(f"/sys/class/gpio/gpio{pin}/value", 'wb') as f:
|
||||
f.write(b"1" if high else b"0")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
for pin in (GPIO.STM_RST_N, GPIO.STM_BOOT0, GPIO.HUB_RST_N):
|
||||
gpio_init(pin, True)
|
||||
|
||||
# reset USB hub
|
||||
gpio_set(GPIO.HUB_RST_N, 0)
|
||||
time.sleep(0.5)
|
||||
gpio_set(GPIO.HUB_RST_N, 1)
|
||||
|
||||
# flash bootstub
|
||||
print("resetting into DFU")
|
||||
gpio_set(GPIO.STM_RST_N, 1)
|
||||
gpio_set(GPIO.STM_BOOT0, 1)
|
||||
time.sleep(1)
|
||||
gpio_set(GPIO.STM_RST_N, 0)
|
||||
gpio_set(GPIO.STM_BOOT0, 0)
|
||||
time.sleep(1)
|
||||
|
||||
print("flashing bootstub")
|
||||
PandaDFU(None).recover()
|
||||
|
||||
gpio_set(GPIO.STM_RST_N, 1)
|
||||
time.sleep(0.5)
|
||||
gpio_set(GPIO.STM_RST_N, 0)
|
||||
time.sleep(1)
|
||||
|
||||
print("flashing app")
|
||||
p = Panda()
|
||||
assert p.bootstub
|
||||
p.flash()
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python
|
||||
import time
|
||||
from panda import Panda
|
||||
|
||||
p = Panda()
|
||||
|
||||
while True:
|
||||
p.set_safety_mode(Panda.SAFETY_TOYOTA)
|
||||
p.send_heartbeat()
|
||||
print("ON")
|
||||
time.sleep(1)
|
||||
p.set_safety_mode(Panda.SAFETY_NOOUTPUT)
|
||||
p.send_heartbeat()
|
||||
print("OFF")
|
||||
time.sleep(1)
|
||||
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
from panda import Panda, PandaDFU, STBootloaderSPIHandle
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
HARDWARE.recover_internal_panda()
|
||||
Panda.wait_for_dfu(None, 5)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
p = PandaDFU(None)
|
||||
assert isinstance(p._handle, STBootloaderSPIHandle)
|
||||
cfg = p.get_mcu_type().config
|
||||
|
||||
print("restoring from backup...")
|
||||
addr = cfg.bootstub_address
|
||||
for i, sector_size in enumerate(cfg.sector_sizes):
|
||||
print(f"- sector #{i}")
|
||||
p._handle.erase_sector(i)
|
||||
with open(f"sector_{i}.bin", "rb") as f:
|
||||
dat = f.read()
|
||||
assert len(dat) == sector_size
|
||||
p._handle.program(addr, dat)
|
||||
addr += len(dat)
|
||||
|
||||
p.reset()
|
||||
@@ -0,0 +1 @@
|
||||
*.bz2
|
||||
@@ -0,0 +1,80 @@
|
||||
import panda.tests.libpanda.libpanda_py as libpanda_py
|
||||
from panda import Panda
|
||||
|
||||
def to_signed(d, bits):
|
||||
ret = d
|
||||
if d >= (1 << (bits - 1)):
|
||||
ret = d - (1 << bits)
|
||||
return ret
|
||||
|
||||
def is_steering_msg(mode, param, addr):
|
||||
ret = False
|
||||
if mode in (Panda.SAFETY_HONDA_NIDEC, Panda.SAFETY_HONDA_BOSCH):
|
||||
ret = (addr == 0xE4) or (addr == 0x194) or (addr == 0x33D) or (addr == 0x33DA) or (addr == 0x33DB)
|
||||
elif mode == Panda.SAFETY_TOYOTA:
|
||||
ret = addr == (0x191 if param & Panda.FLAG_TOYOTA_LTA else 0x2E4)
|
||||
elif mode == Panda.SAFETY_GM:
|
||||
ret = addr == 384
|
||||
elif mode == Panda.SAFETY_HYUNDAI:
|
||||
ret = addr == 832
|
||||
elif mode == Panda.SAFETY_CHRYSLER:
|
||||
ret = addr == 0x292
|
||||
elif mode == Panda.SAFETY_SUBARU:
|
||||
ret = addr == 0x122
|
||||
elif mode == Panda.SAFETY_FORD:
|
||||
ret = addr == 0x3d3
|
||||
elif mode == Panda.SAFETY_NISSAN:
|
||||
ret = addr == 0x169
|
||||
return ret
|
||||
|
||||
def get_steer_value(mode, param, to_send):
|
||||
torque, angle = 0, 0
|
||||
if mode in (Panda.SAFETY_HONDA_NIDEC, Panda.SAFETY_HONDA_BOSCH):
|
||||
torque = (to_send.data[0] << 8) | to_send.data[1]
|
||||
torque = to_signed(torque, 16)
|
||||
elif mode == Panda.SAFETY_TOYOTA:
|
||||
if param & Panda.FLAG_TOYOTA_LTA:
|
||||
angle = (to_send.data[1] << 8) | to_send.data[2]
|
||||
angle = to_signed(angle, 16)
|
||||
else:
|
||||
torque = (to_send.data[1] << 8) | (to_send.data[2])
|
||||
torque = to_signed(torque, 16)
|
||||
elif mode == Panda.SAFETY_GM:
|
||||
torque = ((to_send.data[0] & 0x7) << 8) | to_send.data[1]
|
||||
torque = to_signed(torque, 11)
|
||||
elif mode == Panda.SAFETY_HYUNDAI:
|
||||
torque = (((to_send.data[3] & 0x7) << 8) | to_send.data[2]) - 1024
|
||||
elif mode == Panda.SAFETY_CHRYSLER:
|
||||
torque = (((to_send.data[0] & 0x7) << 8) | to_send.data[1]) - 1024
|
||||
elif mode == Panda.SAFETY_SUBARU:
|
||||
torque = ((to_send.data[3] & 0x1F) << 8) | to_send.data[2]
|
||||
torque = -to_signed(torque, 13)
|
||||
elif mode == Panda.SAFETY_FORD:
|
||||
angle = ((to_send.data[0] << 3) | (to_send.data[1] >> 5)) - 1000
|
||||
elif mode == Panda.SAFETY_NISSAN:
|
||||
angle = (to_send.data[0] << 10) | (to_send.data[1] << 2) | (to_send.data[2] >> 6)
|
||||
angle = -angle + (1310 * 100)
|
||||
return torque, angle
|
||||
|
||||
def package_can_msg(msg):
|
||||
return libpanda_py.make_CANPacket(msg.address, msg.src % 4, msg.dat)
|
||||
|
||||
def init_segment(safety, lr, mode, param):
|
||||
sendcan = (msg for msg in lr if msg.which() == 'sendcan')
|
||||
steering_msgs = (can for msg in sendcan for can in msg.sendcan if is_steering_msg(mode, param, can.address))
|
||||
|
||||
msg = next(steering_msgs, None)
|
||||
if msg is None:
|
||||
# no steering msgs
|
||||
return
|
||||
|
||||
to_send = package_can_msg(msg)
|
||||
torque, angle = get_steer_value(mode, param, to_send)
|
||||
if torque != 0:
|
||||
safety.set_controls_allowed(1)
|
||||
safety.set_desired_torque_last(torque)
|
||||
elif angle != 0:
|
||||
safety.set_controls_allowed(1)
|
||||
safety.set_desired_angle_last(angle)
|
||||
safety.set_angle_meas(angle, angle)
|
||||
assert safety.safety_tx_hook(to_send), "failed to initialize panda safety for segment"
|
||||
Executable
+103
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import os
|
||||
from collections import Counter
|
||||
|
||||
from panda.tests.libpanda import libpanda_py
|
||||
from panda.tests.safety_replay.helpers import package_can_msg, init_segment
|
||||
|
||||
# replay a drive to check for safety violations
|
||||
def replay_drive(lr, safety_mode, param, alternative_experience, segment=False):
|
||||
safety = libpanda_py.libpanda
|
||||
|
||||
err = safety.set_safety_hooks(safety_mode, param)
|
||||
assert err == 0, "invalid safety mode: %d" % safety_mode
|
||||
safety.set_alternative_experience(alternative_experience)
|
||||
|
||||
if segment:
|
||||
init_segment(safety, lr, safety_mode, param)
|
||||
lr.reset()
|
||||
|
||||
rx_tot, rx_invalid, tx_tot, tx_blocked, tx_controls, tx_controls_blocked = 0, 0, 0, 0, 0, 0
|
||||
safety_tick_rx_invalid = False
|
||||
blocked_addrs = Counter()
|
||||
invalid_addrs = set()
|
||||
|
||||
can_msgs = [m for m in lr if m.which() in ('can', 'sendcan')]
|
||||
start_t = can_msgs[0].logMonoTime
|
||||
end_t = can_msgs[-1].logMonoTime
|
||||
for msg in can_msgs:
|
||||
safety.set_timer((msg.logMonoTime // 1000) % 0xFFFFFFFF)
|
||||
|
||||
# skip start and end of route, warm up/down period
|
||||
if msg.logMonoTime - start_t > 1e9 and end_t - msg.logMonoTime > 1e9:
|
||||
safety.safety_tick_current_safety_config()
|
||||
safety_tick_rx_invalid |= not safety.safety_config_valid() or safety_tick_rx_invalid
|
||||
|
||||
if msg.which() == 'sendcan':
|
||||
for canmsg in msg.sendcan:
|
||||
to_send = package_can_msg(canmsg)
|
||||
sent = safety.safety_tx_hook(to_send)
|
||||
if not sent:
|
||||
tx_blocked += 1
|
||||
tx_controls_blocked += safety.get_controls_allowed()
|
||||
blocked_addrs[canmsg.address] += 1
|
||||
|
||||
if "DEBUG" in os.environ:
|
||||
print("blocked bus %d msg %d at %f" % (canmsg.src, canmsg.address, (msg.logMonoTime - start_t) / 1e9))
|
||||
tx_controls += safety.get_controls_allowed()
|
||||
tx_tot += 1
|
||||
elif msg.which() == 'can':
|
||||
# ignore msgs we sent
|
||||
for canmsg in filter(lambda m: m.src < 128, msg.can):
|
||||
to_push = package_can_msg(canmsg)
|
||||
recv = safety.safety_rx_hook(to_push)
|
||||
if not recv:
|
||||
rx_invalid += 1
|
||||
invalid_addrs.add(canmsg.address)
|
||||
rx_tot += 1
|
||||
|
||||
print("\nRX")
|
||||
print("total rx msgs:", rx_tot)
|
||||
print("invalid rx msgs:", rx_invalid)
|
||||
print("safety tick rx invalid:", safety_tick_rx_invalid)
|
||||
print("invalid addrs:", invalid_addrs)
|
||||
print("\nTX")
|
||||
print("total openpilot msgs:", tx_tot)
|
||||
print("total msgs with controls allowed:", tx_controls)
|
||||
print("blocked msgs:", tx_blocked)
|
||||
print("blocked with controls allowed:", tx_controls_blocked)
|
||||
print("blocked addrs:", blocked_addrs)
|
||||
|
||||
return tx_controls_blocked == 0 and rx_invalid == 0 and not safety_tick_rx_invalid
|
||||
|
||||
if __name__ == "__main__":
|
||||
from openpilot.tools.lib.logreader import LogReader
|
||||
|
||||
parser = argparse.ArgumentParser(description="Replay CAN messages from a route or segment through a safety mode",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument("route_or_segment_name", nargs='+')
|
||||
parser.add_argument("--mode", type=int, help="Override the safety mode from the log")
|
||||
parser.add_argument("--param", type=int, help="Override the safety param from the log")
|
||||
parser.add_argument("--alternative-experience", type=int, help="Override the alternative experience from the log")
|
||||
args = parser.parse_args()
|
||||
|
||||
lr = LogReader(args.route_or_segment_name[0])
|
||||
|
||||
if None in (args.mode, args.param, args.alternative_experience):
|
||||
for msg in lr:
|
||||
if msg.which() == 'carParams':
|
||||
if args.mode is None:
|
||||
args.mode = msg.carParams.safetyConfigs[-1].safetyModel.raw
|
||||
if args.param is None:
|
||||
args.param = msg.carParams.safetyConfigs[-1].safetyParam
|
||||
if args.alternative_experience is None:
|
||||
args.alternative_experience = msg.carParams.alternativeExperience
|
||||
break
|
||||
else:
|
||||
raise Exception("carParams not found in log. Set safety mode and param manually.")
|
||||
|
||||
lr.reset()
|
||||
|
||||
print(f"replaying {args.route_or_segment_name[0]} with safety mode {args.mode}, param {args.param}, alternative experience {args.alternative_experience}")
|
||||
replay_drive(lr, args.mode, args.param, args.alternative_experience, segment=len(lr.logreader_identifiers) == 1)
|
||||
Executable
+74
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
if [ -z "$SOURCE_DIR" ]; then
|
||||
echo "SOURCE_DIR must be set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$GIT_COMMIT" ]; then
|
||||
echo "GIT_COMMIT must be set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$TEST_DIR" ]; then
|
||||
echo "TEST_DIR must be set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CONTINUE_PATH="/data/continue.sh"
|
||||
tee $CONTINUE_PATH << EOF
|
||||
#!/usr/bin/bash
|
||||
|
||||
sudo abctl --set_success
|
||||
|
||||
# patch sshd config
|
||||
sudo mount -o rw,remount /
|
||||
sudo sed -i "s,/data/params/d/GithubSshKeys,/usr/comma/setup_keys," /etc/ssh/sshd_config
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl restart ssh
|
||||
sudo systemctl disable ssh-param-watcher.path
|
||||
sudo systemctl disable ssh-param-watcher.service
|
||||
sudo mount -o ro,remount /
|
||||
|
||||
while true; do
|
||||
if ! sudo systemctl is-active -q ssh; then
|
||||
sudo systemctl start ssh
|
||||
fi
|
||||
sleep 5s
|
||||
done
|
||||
|
||||
sleep infinity
|
||||
EOF
|
||||
chmod +x $CONTINUE_PATH
|
||||
|
||||
|
||||
# set up environment
|
||||
if [ ! -d "$SOURCE_DIR" ]; then
|
||||
git clone https://github.com/commaai/panda.git $SOURCE_DIR
|
||||
fi
|
||||
|
||||
# setup device/SOM state
|
||||
SOM_ST_IO=49
|
||||
echo $SOM_ST_IO > /sys/class/gpio/export || true
|
||||
echo out > /sys/class/gpio/gpio${SOM_ST_IO}/direction
|
||||
echo 1 > /sys/class/gpio/gpio${SOM_ST_IO}/value
|
||||
|
||||
# checkout panda commit
|
||||
cd $SOURCE_DIR
|
||||
|
||||
rm -f .git/index.lock
|
||||
git reset --hard
|
||||
git fetch --no-tags --no-recurse-submodules -j4 --verbose --depth 1 origin $GIT_COMMIT
|
||||
find . -maxdepth 1 -not -path './.git' -not -name '.' -not -name '..' -exec rm -rf '{}' \;
|
||||
git reset --hard $GIT_COMMIT
|
||||
git checkout $GIT_COMMIT
|
||||
git clean -xdff
|
||||
|
||||
echo "git checkout done, t=$SECONDS"
|
||||
du -hs $SOURCE_DIR $SOURCE_DIR/.git
|
||||
|
||||
rsync -a --delete $SOURCE_DIR $TEST_DIR
|
||||
|
||||
echo "$TEST_DIR synced with $GIT_COMMIT, t=$SECONDS"
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import time
|
||||
|
||||
from panda import Panda
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
flag_set = False
|
||||
while True:
|
||||
try:
|
||||
with Panda(disable_checks=False) as p:
|
||||
if not flag_set:
|
||||
p.set_heartbeat_disabled()
|
||||
p.set_safety_mode(Panda.SAFETY_ELM327, 30)
|
||||
flag_set = True
|
||||
|
||||
# shutdown when told
|
||||
ch = p.can_health(0)
|
||||
if ch['can_data_speed'] == 1000:
|
||||
os.system("sudo poweroff")
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
time.sleep(0.5)
|
||||
@@ -0,0 +1,153 @@
|
||||
import time
|
||||
import pytest
|
||||
|
||||
from panda import Panda, PandaJungle
|
||||
|
||||
PANDA_SERIAL = "28002d000451323431333839"
|
||||
JUNGLE_SERIAL = "26001c001451313236343430"
|
||||
|
||||
OBDC_PORT = 1
|
||||
|
||||
@pytest.fixture(autouse=True, scope="function")
|
||||
def pj():
|
||||
jungle = PandaJungle(JUNGLE_SERIAL)
|
||||
jungle.flash()
|
||||
|
||||
jungle.reset()
|
||||
jungle.set_ignition(False)
|
||||
|
||||
yield jungle
|
||||
|
||||
#jungle.set_panda_power(False)
|
||||
jungle.close()
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def p(pj):
|
||||
# note that the 3X's panda lib isn't updated, which
|
||||
# shold be fine since it only uses stable APIs
|
||||
pj.set_panda_power(True)
|
||||
assert Panda.wait_for_panda(PANDA_SERIAL, 10)
|
||||
p = Panda(PANDA_SERIAL)
|
||||
p.flash()
|
||||
p.reset()
|
||||
yield p
|
||||
p.close()
|
||||
|
||||
def setup_state(panda, jungle, state):
|
||||
jungle.set_panda_power(0)
|
||||
|
||||
if state == "off":
|
||||
wait_for_full_poweroff(jungle)
|
||||
elif state == "normal boot":
|
||||
jungle.set_panda_individual_power(OBDC_PORT, 1)
|
||||
elif state == "QDL":
|
||||
time.sleep(0.5)
|
||||
jungle.set_panda_individual_power(OBDC_PORT, 1)
|
||||
elif state == "ready to bootkick":
|
||||
wait_for_full_poweroff(jungle)
|
||||
jungle.set_panda_individual_power(OBDC_PORT, 1)
|
||||
wait_for_boot(panda, jungle)
|
||||
set_som_shutdown_flag(panda)
|
||||
panda.set_safety_mode(Panda.SAFETY_SILENT)
|
||||
panda.send_heartbeat()
|
||||
wait_for_som_shutdown(panda, jungle)
|
||||
else:
|
||||
raise ValueError(f"unkown state: {state}")
|
||||
|
||||
|
||||
def wait_for_som_shutdown(panda, jungle):
|
||||
st = time.monotonic()
|
||||
while panda.read_som_gpio():
|
||||
# can take a while for the SOM to fully shutdown
|
||||
if time.monotonic() - st > 120:
|
||||
raise Exception("SOM didn't shutdown in time")
|
||||
if check_som_boot_flag(panda):
|
||||
raise Exception(f"SOM rebooted instead of shutdown: {time.monotonic() - st}s")
|
||||
time.sleep(0.5)
|
||||
dt = time.monotonic() - st
|
||||
print("waiting for shutdown", round(dt))
|
||||
dt = time.monotonic() - st
|
||||
print(f"took {dt:.2f}s for SOM to shutdown")
|
||||
|
||||
def wait_for_full_poweroff(jungle, timeout=30):
|
||||
st = time.monotonic()
|
||||
|
||||
time.sleep(15)
|
||||
while PANDA_SERIAL in Panda.list():
|
||||
if time.monotonic() - st > timeout:
|
||||
raise Exception("took too long for device to turn off")
|
||||
|
||||
health = jungle.health()
|
||||
assert all(health[f"ch{i}_power"] < 0.1 for i in range(1, 7))
|
||||
|
||||
def check_som_boot_flag(panda):
|
||||
h = panda.health()
|
||||
return h['safety_mode'] == Panda.SAFETY_ELM327 and h['safety_param'] == 30
|
||||
|
||||
def set_som_shutdown_flag(panda):
|
||||
panda.set_can_data_speed_kbps(0, 1000)
|
||||
|
||||
def wait_for_boot(panda, jungle, reset_expected=False, bootkick=False, timeout=120):
|
||||
st = time.monotonic()
|
||||
|
||||
Panda.wait_for_panda(PANDA_SERIAL, timeout)
|
||||
panda.reconnect()
|
||||
if bootkick:
|
||||
assert panda.health()['uptime'] > 20
|
||||
else:
|
||||
assert panda.health()['uptime'] < 3
|
||||
|
||||
for i in range(3):
|
||||
assert not check_som_boot_flag(panda)
|
||||
time.sleep(1)
|
||||
|
||||
# wait for SOM to bootup
|
||||
while not check_som_boot_flag(panda):
|
||||
if time.monotonic() - st > timeout:
|
||||
raise Exception("SOM didn't boot in time")
|
||||
time.sleep(1.0)
|
||||
|
||||
assert panda.health()['som_reset_triggered'] == reset_expected
|
||||
|
||||
def test_cold_boot(p, pj):
|
||||
setup_state(p, pj, "off")
|
||||
setup_state(p, pj, "normal boot")
|
||||
wait_for_boot(p, pj)
|
||||
|
||||
def test_bootkick_ignition_line(p, pj):
|
||||
setup_state(p, pj, "ready to bootkick")
|
||||
pj.set_ignition(True)
|
||||
wait_for_boot(p, pj, bootkick=True)
|
||||
|
||||
@pytest.mark.skip("test isn't reliable yet")
|
||||
def test_bootkick_can_ignition(p, pj):
|
||||
setup_state(p, pj, "ready to bootkick")
|
||||
for _ in range(10):
|
||||
# Mazda ignition signal
|
||||
pj.can_send(0x9E, b'\xc0\x00\x00\x00\x00\x00\x00\x00', 0)
|
||||
time.sleep(0.5)
|
||||
wait_for_boot(p, pj, bootkick=True)
|
||||
|
||||
def test_recovery_from_qdl(p, pj):
|
||||
setup_state(p, pj, "ready to bootkick")
|
||||
|
||||
# put into QDL using the FORCE_USB_BOOT pin
|
||||
for i in range(10):
|
||||
pj.set_header_pin(i, 1)
|
||||
|
||||
# try to boot
|
||||
time.sleep(1)
|
||||
pj.set_ignition(True)
|
||||
time.sleep(3)
|
||||
|
||||
# release FORCE_USB_BOOT
|
||||
for i in range(10):
|
||||
pj.set_header_pin(i, 0)
|
||||
|
||||
# normally, this GPIO is set immediately since it's first enabled in the ABL
|
||||
for i in range(17):
|
||||
assert not p.read_som_gpio()
|
||||
time.sleep(1)
|
||||
|
||||
# should boot after 45s
|
||||
wait_for_boot(p, pj, reset_expected=True, bootkick=True, timeout=120)
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/bash
|
||||
set -e
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
|
||||
cd $DIR
|
||||
|
||||
PYTHONUNBUFFERED=1 NO_COLOR=1 CLAIM=1 PORT=4 ./debug_console.py
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import random
|
||||
|
||||
from panda import Panda
|
||||
|
||||
def get_test_string():
|
||||
return b"test" + os.urandom(10)
|
||||
|
||||
if __name__ == "__main__":
|
||||
p = Panda()
|
||||
p.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
|
||||
print("Spamming all buses...")
|
||||
while True:
|
||||
at = random.randint(1, 2000)
|
||||
st = get_test_string()[0:8]
|
||||
bus = random.randint(0, 2)
|
||||
p.can_send(at, st, bus)
|
||||
# print("Sent message on bus: ", bus)
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python3
|
||||
import struct
|
||||
import time
|
||||
|
||||
from panda import Panda
|
||||
|
||||
if __name__ == "__main__":
|
||||
p = Panda()
|
||||
print(p.get_serial())
|
||||
print(p.health())
|
||||
|
||||
t1 = time.time()
|
||||
for _ in range(100):
|
||||
p.get_serial()
|
||||
t2 = time.time()
|
||||
print("100 requests took %.2f ms" % ((t2 - t1) * 1000))
|
||||
|
||||
p.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
|
||||
|
||||
a = 0
|
||||
while True:
|
||||
# flood
|
||||
msg = b"\xaa" * 4 + struct.pack("I", a)
|
||||
p.can_send(0xaa, msg, 0)
|
||||
p.can_send(0xaa, msg, 1)
|
||||
p.can_send(0xaa, msg, 4)
|
||||
time.sleep(0.01)
|
||||
|
||||
dat = p.can_recv()
|
||||
if len(dat) > 0:
|
||||
print(dat)
|
||||
a += 1
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
gcc -DTEST_RSA test_rsa.c ../crypto/rsa.c ../crypto/sha.c && ./a.out
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define MAX_LEN 0x40000
|
||||
char buf[MAX_LEN];
|
||||
|
||||
#include "../crypto/sha.h"
|
||||
#include "../crypto/rsa.h"
|
||||
#include "../obj/cert.h"
|
||||
|
||||
int main() {
|
||||
FILE *f = fopen("../obj/panda.bin", "rb");
|
||||
int tlen = fread(buf, 1, MAX_LEN, f);
|
||||
fclose(f);
|
||||
printf("read %d\n", tlen);
|
||||
uint32_t *_app_start = (uint32_t *)buf;
|
||||
|
||||
int len = _app_start[0];
|
||||
char digest[SHA_DIGEST_SIZE];
|
||||
SHA_hash(&_app_start[1], len-4, digest);
|
||||
printf("SHA hash done\n");
|
||||
|
||||
if (!RSA_verify(&rsa_key, ((void*)&_app_start[0]) + len, RSANUMBYTES, digest, SHA_DIGEST_SIZE)) {
|
||||
printf("RSA fail\n");
|
||||
} else {
|
||||
printf("RSA match!!!\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
# Loops over all HW_TYPEs, see board/boards/board_declarations.h
|
||||
for hw_type in {0..7}; do
|
||||
echo "Testing HW_TYPE: $hw_type"
|
||||
HW_TYPE=$hw_type python -m unittest discover .
|
||||
done
|
||||
Executable
+160
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python3
|
||||
import random
|
||||
import unittest
|
||||
|
||||
from panda import Panda, DLC_TO_LEN, USBPACKET_MAX_SIZE, pack_can_buffer, unpack_can_buffer
|
||||
from panda.tests.libpanda import libpanda_py
|
||||
|
||||
lpp = libpanda_py.libpanda
|
||||
|
||||
CHUNK_SIZE = USBPACKET_MAX_SIZE
|
||||
TX_QUEUES = (lpp.tx1_q, lpp.tx2_q, lpp.tx3_q)
|
||||
|
||||
|
||||
def unpackage_can_msg(pkt):
|
||||
dat_len = DLC_TO_LEN[pkt[0].data_len_code]
|
||||
dat = bytes(pkt[0].data[0:dat_len])
|
||||
return pkt[0].addr, 0, dat, pkt[0].bus
|
||||
|
||||
|
||||
def random_can_messages(n, bus=None):
|
||||
msgs = []
|
||||
for _ in range(n):
|
||||
if bus is None:
|
||||
bus = random.randint(0, 3)
|
||||
address = random.randint(1, (1 << 29) - 1)
|
||||
data = bytes([random.getrandbits(8) for _ in range(DLC_TO_LEN[random.randrange(0, len(DLC_TO_LEN))])])
|
||||
msgs.append((address, 0, data, bus))
|
||||
return msgs
|
||||
|
||||
|
||||
class TestPandaComms(unittest.TestCase):
|
||||
def setUp(self):
|
||||
lpp.comms_can_reset()
|
||||
|
||||
def test_tx_queues(self):
|
||||
for bus in range(len(TX_QUEUES)):
|
||||
message = (0x100, 0, b"test", bus)
|
||||
|
||||
can_pkt_tx = libpanda_py.make_CANPacket(message[0], message[3], message[2])
|
||||
can_pkt_rx = libpanda_py.ffi.new('CANPacket_t *')
|
||||
|
||||
assert lpp.can_push(TX_QUEUES[bus], can_pkt_tx), "CAN push failed"
|
||||
assert lpp.can_pop(TX_QUEUES[bus], can_pkt_rx), "CAN pop failed"
|
||||
|
||||
assert unpackage_can_msg(can_pkt_rx) == message
|
||||
|
||||
def test_comms_reset_rx(self):
|
||||
# store some test messages in the queue
|
||||
test_msg = (0x100, 0, b"test", 0)
|
||||
for _ in range(100):
|
||||
can_pkt_tx = libpanda_py.make_CANPacket(test_msg[0], test_msg[3], test_msg[2])
|
||||
lpp.can_push(lpp.rx_q, can_pkt_tx)
|
||||
|
||||
# read a small chunk such that we have some overflow
|
||||
TINY_CHUNK_SIZE = 6
|
||||
dat = libpanda_py.ffi.new(f"uint8_t[{TINY_CHUNK_SIZE}]")
|
||||
rx_len = lpp.comms_can_read(dat, TINY_CHUNK_SIZE)
|
||||
assert rx_len == TINY_CHUNK_SIZE, "comms_can_read returned too little data"
|
||||
|
||||
_, overflow = unpack_can_buffer(bytes(dat))
|
||||
assert len(overflow) > 0, "overflow buffer should not be empty"
|
||||
|
||||
# reset the comms to clear the overflow buffer on the panda side
|
||||
lpp.comms_can_reset()
|
||||
|
||||
# read a large chunk, which should now contain valid messages
|
||||
LARGE_CHUNK_SIZE = 512
|
||||
dat = libpanda_py.ffi.new(f"uint8_t[{LARGE_CHUNK_SIZE}]")
|
||||
rx_len = lpp.comms_can_read(dat, LARGE_CHUNK_SIZE)
|
||||
assert rx_len == LARGE_CHUNK_SIZE, "comms_can_read returned too little data"
|
||||
|
||||
msgs, _ = unpack_can_buffer(bytes(dat))
|
||||
assert len(msgs) > 0, "message buffer should not be empty"
|
||||
for m in msgs:
|
||||
assert m == test_msg, "message buffer should contain valid test messages"
|
||||
|
||||
def test_comms_reset_tx(self):
|
||||
# store some test messages in the queue
|
||||
test_msg = (0x100, 0, b"test", 0)
|
||||
packed = pack_can_buffer([test_msg for _ in range(100)])
|
||||
|
||||
# write a small chunk such that we have some overflow
|
||||
TINY_CHUNK_SIZE = 6
|
||||
lpp.comms_can_write(packed[0][:TINY_CHUNK_SIZE], TINY_CHUNK_SIZE)
|
||||
|
||||
# reset the comms to clear the overflow buffer on the panda side
|
||||
lpp.comms_can_reset()
|
||||
|
||||
# write a full valid chunk, which should now contain valid messages
|
||||
lpp.comms_can_write(packed[1], len(packed[1]))
|
||||
|
||||
# read the messages from the queue and make sure they're valid
|
||||
queue_msgs = []
|
||||
pkt = libpanda_py.ffi.new('CANPacket_t *')
|
||||
while lpp.can_pop(TX_QUEUES[0], pkt):
|
||||
queue_msgs.append(unpackage_can_msg(pkt))
|
||||
|
||||
assert len(queue_msgs) > 0, "message buffer should not be empty"
|
||||
for m in queue_msgs:
|
||||
assert m == test_msg, "message buffer should contain valid test messages"
|
||||
|
||||
|
||||
def test_can_send_usb(self):
|
||||
lpp.set_safety_hooks(Panda.SAFETY_ALLOUTPUT, 0)
|
||||
|
||||
for bus in range(3):
|
||||
with self.subTest(bus=bus):
|
||||
for _ in range(100):
|
||||
msgs = random_can_messages(200, bus=bus)
|
||||
packed = pack_can_buffer(msgs)
|
||||
|
||||
# Simulate USB bulk chunks
|
||||
for buf in packed:
|
||||
for i in range(0, len(buf), CHUNK_SIZE):
|
||||
chunk_len = min(CHUNK_SIZE, len(buf) - i)
|
||||
lpp.comms_can_write(buf[i:i+chunk_len], chunk_len)
|
||||
|
||||
# Check that they ended up in the right buffers
|
||||
queue_msgs = []
|
||||
pkt = libpanda_py.ffi.new('CANPacket_t *')
|
||||
while lpp.can_pop(TX_QUEUES[bus], pkt):
|
||||
queue_msgs.append(unpackage_can_msg(pkt))
|
||||
|
||||
self.assertEqual(len(queue_msgs), len(msgs))
|
||||
self.assertEqual(queue_msgs, msgs)
|
||||
|
||||
def test_can_receive_usb(self):
|
||||
msgs = random_can_messages(50000)
|
||||
packets = [libpanda_py.make_CANPacket(m[0], m[3], m[2]) for m in msgs]
|
||||
|
||||
rx_msgs = []
|
||||
overflow_buf = b""
|
||||
while len(packets) > 0:
|
||||
# Push into queue
|
||||
while lpp.can_slots_empty(lpp.rx_q) > 0 and len(packets) > 0:
|
||||
lpp.can_push(lpp.rx_q, packets.pop(0))
|
||||
|
||||
# Simulate USB bulk IN chunks
|
||||
MAX_TRANSFER_SIZE = 16384
|
||||
dat = libpanda_py.ffi.new(f"uint8_t[{CHUNK_SIZE}]")
|
||||
while True:
|
||||
buf = b""
|
||||
while len(buf) < MAX_TRANSFER_SIZE:
|
||||
max_size = min(CHUNK_SIZE, MAX_TRANSFER_SIZE - len(buf))
|
||||
rx_len = lpp.comms_can_read(dat, max_size)
|
||||
buf += bytes(dat[0:rx_len])
|
||||
if rx_len < max_size:
|
||||
break
|
||||
|
||||
if len(buf) == 0:
|
||||
break
|
||||
unpacked_msgs, overflow_buf = unpack_can_buffer(overflow_buf + buf)
|
||||
rx_msgs.extend(unpacked_msgs)
|
||||
|
||||
self.assertEqual(len(rx_msgs), len(msgs))
|
||||
self.assertEqual(rx_msgs, msgs)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
import random
|
||||
import unittest
|
||||
|
||||
from panda import pack_can_buffer, unpack_can_buffer, DLC_TO_LEN
|
||||
|
||||
class PandaTestPackUnpack(unittest.TestCase):
|
||||
def test_panda_lib_pack_unpack(self):
|
||||
overflow_buf = b''
|
||||
|
||||
to_pack = []
|
||||
for _ in range(10000):
|
||||
address = random.randint(1, (1 << 29) - 1)
|
||||
data = bytes([random.getrandbits(8) for _ in range(DLC_TO_LEN[random.randrange(0, len(DLC_TO_LEN))])])
|
||||
to_pack.append((address, 0, data, 0))
|
||||
|
||||
packed = pack_can_buffer(to_pack)
|
||||
unpacked = []
|
||||
for dat in packed:
|
||||
msgs, overflow_buf = unpack_can_buffer(overflow_buf + dat)
|
||||
unpacked.extend(msgs)
|
||||
|
||||
self.assertEqual(unpacked, to_pack)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user