You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
			
				
					167 lines
				
				5.7 KiB
			
		
		
			
		
	
	
					167 lines
				
				5.7 KiB
			| 
											6 years ago
										 | #!/usr/bin/env python3
 | ||
| 
											1 year ago
										 | # simple pandad wrapper that updates the panda first
 | ||
| 
											6 years ago
										 | import os
 | ||
| 
											4 years ago
										 | import usb1
 | ||
| 
											6 years ago
										 | import time
 | ||
| 
											4 years ago
										 | import subprocess
 | ||
| 
											2 years ago
										 | from typing import NoReturn
 | ||
| 
											6 years ago
										 | 
 | ||
| 
											2 years ago
										 | from panda import Panda, PandaDFU, PandaProtocolMismatch, FW_PATH
 | ||
| 
											2 years ago
										 | from openpilot.common.basedir import BASEDIR
 | ||
|  | from openpilot.common.params import Params
 | ||
|  | from openpilot.system.hardware import HARDWARE
 | ||
| 
											2 years ago
										 | from openpilot.common.swaglog import cloudlog
 | ||
| 
											5 years ago
										 | 
 | ||
| 
											5 years ago
										 | 
 | ||
| 
											4 years ago
										 | def get_expected_signature(panda: Panda) -> bytes:
 | ||
| 
											5 years ago
										 |   try:
 | ||
| 
											2 years ago
										 |     fn = os.path.join(FW_PATH, panda.get_mcu_type().config.app_fn)
 | ||
|  |     return Panda.get_signature_from_firmware(fn)
 | ||
| 
											5 years ago
										 |   except Exception:
 | ||
|  |     cloudlog.exception("Error computing expected signature")
 | ||
|  |     return b""
 | ||
| 
											6 years ago
										 | 
 | ||
| 
											4 years ago
										 | def flash_panda(panda_serial: str) -> Panda:
 | ||
| 
											2 years ago
										 |   try:
 | ||
|  |     panda = Panda(panda_serial)
 | ||
|  |   except PandaProtocolMismatch:
 | ||
|  |     cloudlog.warning("detected protocol mismatch, reflashing panda")
 | ||
|  |     HARDWARE.recover_internal_panda()
 | ||
|  |     raise
 | ||
| 
											6 years ago
										 | 
 | ||
| 
											4 years ago
										 |   fw_signature = get_expected_signature(panda)
 | ||
| 
											3 years ago
										 |   internal_panda = panda.is_internal()
 | ||
| 
											6 years ago
										 | 
 | ||
|  |   panda_version = "bootstub" if panda.bootstub else panda.get_version()
 | ||
| 
											6 years ago
										 |   panda_signature = b"" if panda.bootstub else panda.get_signature()
 | ||
| 
											4 years ago
										 |   cloudlog.warning(f"Panda {panda_serial} connected, version: {panda_version}, signature {panda_signature.hex()[:16]}, expected {fw_signature.hex()[:16]}")
 | ||
| 
											6 years ago
										 | 
 | ||
|  |   if panda.bootstub or panda_signature != fw_signature:
 | ||
|  |     cloudlog.info("Panda firmware out of date, update required")
 | ||
| 
											5 years ago
										 |     panda.flash()
 | ||
| 
											6 years ago
										 |     cloudlog.info("Done flashing")
 | ||
|  | 
 | ||
|  |   if panda.bootstub:
 | ||
| 
											6 years ago
										 |     bootstub_version = panda.get_version()
 | ||
| 
											3 years ago
										 |     cloudlog.info(f"Flashed firmware not booting, flashing development bootloader. {bootstub_version=}, {internal_panda=}")
 | ||
| 
											4 years ago
										 |     if internal_panda:
 | ||
|  |       HARDWARE.recover_internal_panda()
 | ||
|  |     panda.recover(reset=(not internal_panda))
 | ||
| 
											2 years ago
										 |     cloudlog.info("Done flashing bootstub")
 | ||
| 
											6 years ago
										 | 
 | ||
|  |   if panda.bootstub:
 | ||
|  |     cloudlog.info("Panda still not booting, exiting")
 | ||
|  |     raise AssertionError
 | ||
|  | 
 | ||
|  |   panda_signature = panda.get_signature()
 | ||
|  |   if panda_signature != fw_signature:
 | ||
|  |     cloudlog.info("Version mismatch after flashing, exiting")
 | ||
|  |     raise AssertionError
 | ||
|  | 
 | ||
| 
											4 years ago
										 |   return panda
 | ||
|  | 
 | ||
| 
											4 years ago
										 | 
 | ||
| 
											4 years ago
										 | def main() -> NoReturn:
 | ||
| 
											3 years ago
										 |   count = 0
 | ||
| 
											4 years ago
										 |   first_run = True
 | ||
| 
											4 years ago
										 |   params = Params()
 | ||
| 
											2 years ago
										 |   no_internal_panda_count = 0
 | ||
| 
											4 years ago
										 | 
 | ||
| 
											4 years ago
										 |   while True:
 | ||
|  |     try:
 | ||
| 
											3 years ago
										 |       count += 1
 | ||
|  |       cloudlog.event("pandad.flash_and_connect", count=count)
 | ||
| 
											3 years ago
										 |       params.remove("PandaSignatures")
 | ||
| 
											4 years ago
										 | 
 | ||
| 
											2 years ago
										 |       # TODO: remove this in the next AGNOS
 | ||
|  |       # wait until USB is up before counting
 | ||
|  |       if time.monotonic() < 25.:
 | ||
|  |         no_internal_panda_count = 0
 | ||
|  | 
 | ||
| 
											2 years ago
										 |       # 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
 | ||
|  | 
 | ||
| 
											4 years ago
										 |       # Flash all Pandas in DFU mode
 | ||
| 
											3 years ago
										 |       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()
 | ||
|  |         time.sleep(1)
 | ||
| 
											4 years ago
										 | 
 | ||
|  |       panda_serials = Panda.list()
 | ||
|  |       if len(panda_serials) == 0:
 | ||
| 
											2 years ago
										 |         no_internal_panda_count += 1
 | ||
| 
											4 years ago
										 |         continue
 | ||
|  | 
 | ||
|  |       cloudlog.info(f"{len(panda_serials)} panda(s) found, connecting - {panda_serials}")
 | ||
|  | 
 | ||
|  |       # Flash pandas
 | ||
| 
											2 years ago
										 |       pandas: list[Panda] = []
 | ||
| 
											4 years ago
										 |       for serial in panda_serials:
 | ||
|  |         pandas.append(flash_panda(serial))
 | ||
|  | 
 | ||
| 
											3 years ago
										 |       # Ensure internal panda is present if expected
 | ||
|  |       internal_pandas = [panda for panda in pandas if panda.is_internal()]
 | ||
|  |       if HARDWARE.has_internal_panda() and len(internal_pandas) == 0:
 | ||
| 
											2 years ago
										 |         cloudlog.error("Internal panda is missing, trying again")
 | ||
|  |         no_internal_panda_count += 1
 | ||
| 
											3 years ago
										 |         continue
 | ||
| 
											2 years ago
										 |       no_internal_panda_count = 0
 | ||
| 
											3 years ago
										 | 
 | ||
| 
											4 years ago
										 |       # sort pandas to have deterministic order
 | ||
| 
											2 years ago
										 |       # * the internal one is always first
 | ||
|  |       # * then sort by hardware type
 | ||
|  |       # * as a last resort, sort by serial number
 | ||
|  |       pandas.sort(key=lambda x: (not x.is_internal(), x.get_type(), x.get_usb_serial()))
 | ||
| 
											2 years ago
										 |       panda_serials = [p.get_usb_serial() for p in pandas]
 | ||
| 
											4 years ago
										 | 
 | ||
| 
											4 years ago
										 |       # log panda fw versions
 | ||
|  |       params.put("PandaSignatures", b','.join(p.get_signature() for p in pandas))
 | ||
|  | 
 | ||
| 
											3 years ago
										 |       for panda in pandas:
 | ||
|  |         # 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())
 | ||
| 
											2 years ago
										 |         if health["som_reset_triggered"]:
 | ||
|  |           params.put_bool("PandaSomResetTriggered", True)
 | ||
|  |           cloudlog.event("panda.som_reset_triggered", health=health, serial=panda.get_usb_serial())
 | ||
| 
											3 years ago
										 | 
 | ||
|  |         if first_run:
 | ||
| 
											2 years ago
										 |           # reset panda to ensure we're in a good state
 | ||
| 
											3 years ago
										 |           cloudlog.info(f"Resetting panda {panda.get_usb_serial()}")
 | ||
| 
											2 years ago
										 |           panda.reset(reconnect=True)
 | ||
| 
											3 years ago
										 | 
 | ||
| 
											4 years ago
										 |       for p in pandas:
 | ||
|  |         p.close()
 | ||
| 
											3 years ago
										 |     # TODO: wrap all panda exceptions in a base panda exception
 | ||
| 
											4 years ago
										 |     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
 | ||
| 
											2 years ago
										 |     except PandaProtocolMismatch:
 | ||
|  |       cloudlog.exception("pandad.protocol_mismatch")
 | ||
|  |       continue
 | ||
| 
											3 years ago
										 |     except Exception:
 | ||
|  |       cloudlog.exception("pandad.uncaught_exception")
 | ||
|  |       continue
 | ||
| 
											4 years ago
										 | 
 | ||
| 
											4 years ago
										 |     first_run = False
 | ||
|  | 
 | ||
| 
											1 year ago
										 |     # run pandad with all connected serials as arguments
 | ||
|  |     os.environ['MANAGER_DAEMON'] = 'pandad'
 | ||
|  |     os.chdir(os.path.join(BASEDIR, "selfdrive/pandad"))
 | ||
|  |     subprocess.run(["./pandad", *panda_serials], check=True)
 | ||
| 
											5 years ago
										 | 
 | ||
| 
											6 years ago
										 | if __name__ == "__main__":
 | ||
|  |   main()
 |