Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions common/params_keys.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"OnroadCycleRequested", {CLEAR_ON_MANAGER_START, BOOL}},
{"OpenpilotEnabledToggle", {PERSISTENT, BOOL, "1"}},
{"PandaHeartbeatLost", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
{"PandaSomResetTriggered", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
{"PandaSignatures", {CLEAR_ON_MANAGER_START, BYTES}},
{"PrimeType", {PERSISTENT, INT}},
{"RecordAudio", {PERSISTENT, BOOL}},
{"RecordAudioFeedback", {PERSISTENT, BOOL, "0"}},
Expand Down
114 changes: 33 additions & 81 deletions selfdrive/pandad/pandad.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,11 @@


def get_expected_signature() -> bytes:
try:
fn = os.path.join(FW_PATH, McuType.H7.config.app_fn)
return Panda.get_signature_from_firmware(fn)
except Exception:
cloudlog.exception("Error computing expected signature")
return b""

def flash_panda(panda_serial: str) -> Panda:
try:
panda = Panda(panda_serial)
except PandaProtocolMismatch:
cloudlog.warning("detected protocol mismatch, reflashing panda")
HARDWARE.recover_internal_panda()
raise
fn = os.path.join(FW_PATH, McuType.H7.config.app_fn)
return Panda.get_signature_from_firmware(fn)

def flash_panda(panda_serial: str):
panda = Panda(panda_serial)
fw_signature = get_expected_signature()
internal_panda = panda.is_internal()

Expand Down Expand Up @@ -58,7 +48,7 @@ def flash_panda(panda_serial: str) -> Panda:
cloudlog.info("Version mismatch after flashing, exiting")
raise AssertionError

return panda
panda.close()


def main() -> None:
Expand All @@ -74,89 +64,51 @@ def signal_handler(signum, frame):
do_exit = False
signal.signal(signal.SIGINT, signal_handler)

count = 0
first_run = True
params = Params()
no_internal_panda_count = 0
# check health for lost heartbeat
try:
for s in Panda.list():
with Panda(s) as p:
health = p.health()
if p.is_internal() and health["heartbeat_lost"]:
Params().put_bool("PandaHeartbeatLost", True)
cloudlog.event("heartbeat lost", deviceState=health)
except Exception:
cloudlog.exception("pandad.uncaught_exception")

count = 0
while not do_exit:
try:
count += 1
cloudlog.event("pandad.flash_and_connect", count=count)
params.remove("PandaSignatures")

# Handle missing internal panda
if no_internal_panda_count > 0:
if no_internal_panda_count == 3:
cloudlog.info("No pandas found, putting internal panda into DFU")
HARDWARE.recover_internal_panda()
else:
cloudlog.info("No pandas found, resetting internal panda")
HARDWARE.reset_internal_panda()
time.sleep(3) # wait to come back up
if (count % 2) == 0:
HARDWARE.reset_internal_panda()
else:
HARDWARE.recover_internal_panda()
count += 1

# Flash all Pandas in DFU mode
dfu_serials = PandaDFU.list()
if len(dfu_serials) > 0:
for serial in dfu_serials:
cloudlog.info(f"Panda in DFU mode found, flashing recovery {serial}")
PandaDFU(serial).recover()
for serial in PandaDFU.list():
cloudlog.info(f"Panda in DFU mode found, flashing recovery {serial}")
PandaDFU(serial).recover()
time.sleep(1)

panda_serials = Panda.list()
if len(panda_serials) == 0:
no_internal_panda_count += 1
continue

cloudlog.info(f"{len(panda_serials)} panda(s) found, connecting - {panda_serials}")

# Flash the first panda
panda_serial = panda_serials[0]
panda = flash_panda(panda_serial)

# Ensure internal panda is present if expected
if HARDWARE.has_internal_panda() and not panda.is_internal():
cloudlog.error("Internal panda is missing, trying again")
no_internal_panda_count += 1
continue
no_internal_panda_count = 0

# log panda fw version
params.put("PandaSignatures", panda.get_signature())

# check health for lost heartbeat
health = panda.health()
if health["heartbeat_lost"]:
params.put_bool("PandaHeartbeatLost", True)
cloudlog.event("heartbeat lost", deviceState=health, serial=panda.get_usb_serial())
if health["som_reset_triggered"]:
params.put_bool("PandaSomResetTriggered", True)
cloudlog.event("panda.som_reset_triggered", health=health, serial=panda.get_usb_serial())

if first_run:
# reset panda to ensure we're in a good state
cloudlog.info(f"Resetting panda {panda.get_usb_serial()}")
panda.reset(reconnect=True)

panda.close()
if len(panda_serials):
assert len(panda_serials) == 1
cloudlog.info(f"{len(panda_serials)} panda found, connecting - {panda_serials}")
flash_panda(panda_serials[0])

# run real pandad
os.environ['MANAGER_DAEMON'] = 'pandad'
process = subprocess.Popen(["./pandad"], cwd=os.path.join(BASEDIR, "selfdrive/pandad"))
process.wait()
# TODO: wrap all panda exceptions in a base panda exception
except (usb1.USBErrorNoDevice, usb1.USBErrorPipe):
# a panda was disconnected while setting everything up. let's try again
cloudlog.exception("Panda USB exception while setting up")
continue
except PandaProtocolMismatch:
cloudlog.exception("pandad.protocol_mismatch")
continue
except Exception:
cloudlog.exception("pandad.uncaught_exception")
continue

first_run = False

# run pandad with all connected serials as arguments
os.environ['MANAGER_DAEMON'] = 'pandad'
process = subprocess.Popen(["./pandad", panda_serial], cwd=os.path.join(BASEDIR, "selfdrive/pandad"))
process.wait()


if __name__ == "__main__":
Expand Down
Binary file removed selfdrive/pandad/tests/bootstub.panda_h7_spiv0.bin
Binary file not shown.
39 changes: 11 additions & 28 deletions selfdrive/pandad/tests/test_pandad.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,6 @@

@pytest.mark.tici
class TestPandad:

def setup_method(self):
# ensure panda is up
if len(Panda.list()) == 0:
self._run_test(60)

def teardown_method(self):
managed_processes['pandad'].stop()

Expand All @@ -30,7 +24,7 @@ def _run_test(self, timeout=30) -> float:

managed_processes['pandad'].start()
while (time.monotonic() - st) < timeout:
sm.update(100)
sm.update(10)
if len(sm['pandaStates']) and sm['pandaStates'][0].pandaType != log.PandaState.PandaType.unknown:
break
dt = time.monotonic() - st
Expand All @@ -45,54 +39,43 @@ def _go_to_dfu(self):
HARDWARE.recover_internal_panda()
assert Panda.wait_for_dfu(None, 10)

def _assert_no_panda(self):
assert not Panda.wait_for_dfu(None, 3)
assert not Panda.wait_for_panda(None, 3)

def _flash_bootstub(self, fn):
self._go_to_dfu()
pd = PandaDFU(None)
if fn is None:
fn = os.path.join(HERE, pd.get_mcu_type().config.bootstub_fn)
with open(fn, "rb") as f:
pd.program_bootstub(f.read())
pd.reset()
HARDWARE.reset_internal_panda()

def test_in_dfu(self):
HARDWARE.recover_internal_panda()
self._run_test(60)
self._run_test()

def test_in_bootstub(self):
with Panda() as p:
p.reset(enter_bootstub=True)
assert p.bootstub
self._run_test()

def test_internal_panda_reset(self):
def test_in_reset(self):
gpio_init(GPIO.STM_RST_N, True)
gpio_set(GPIO.STM_RST_N, 1)
time.sleep(0.5)
assert all(not Panda(s).is_internal() for s in Panda.list())
assert not Panda.list()
self._run_test()

assert any(Panda(s).is_internal() for s in Panda.list())

def test_old_spi_protocol(self):
# flash firmware with old SPI protocol
self._flash_bootstub(os.path.join(HERE, "bootstub.panda_h7_spiv0.bin"))
self._run_test(45)

def test_release_to_devel_bootstub(self):
st = time.monotonic()
self._flash_bootstub(None)
self._run_test(45)
print("flash done", time.monotonic() - st)
self._run_test()

def test_recover_from_bad_bootstub(self):
self._go_to_dfu()
with PandaDFU(None) as pd:
pd.program_bootstub(b"\x00"*1024)
pd.reset()
pd._handle.program(pd.get_mcu_type().config.bootstub_address, b"\x00"*100)
HARDWARE.reset_internal_panda()
self._assert_no_panda()
assert not Panda.list()
assert not PandaDFU.list()

self._run_test(60)
self._run_test()
6 changes: 3 additions & 3 deletions system/hardware/tici/hardware.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ def reset_internal_panda(self):

gpio_set(GPIO.STM_RST_N, 1)
gpio_set(GPIO.STM_BOOT0, 0)
time.sleep(1)
time.sleep(0.01)
gpio_set(GPIO.STM_RST_N, 0)

def recover_internal_panda(self):
Expand All @@ -423,9 +423,9 @@ def recover_internal_panda(self):

gpio_set(GPIO.STM_RST_N, 1)
gpio_set(GPIO.STM_BOOT0, 1)
time.sleep(0.5)
time.sleep(0.01)
gpio_set(GPIO.STM_RST_N, 0)
time.sleep(0.5)
time.sleep(0.01)
gpio_set(GPIO.STM_BOOT0, 0)

def booted(self):
Expand Down
Loading