From e1e9efb965775a1b14c8d78929ac83915b58a1a7 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Tue, 30 Jun 2026 16:17:26 -0700 Subject: [PATCH] add USB logging (#38186) * usb logging * fix test * discover by PID * hardwared, add counters * edit msg * add manufacturer * reorder * rm portli * rm this --- openpilot/cereal/log.capnp | 18 ++++++++ openpilot/common/hardware/usb.py | 63 ++++++++++++++++++++++++++ openpilot/system/hardware/hardwared.py | 7 ++- 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 openpilot/common/hardware/usb.py diff --git a/openpilot/cereal/log.capnp b/openpilot/cereal/log.capnp index 8d4d80116..2b43a95d7 100644 --- a/openpilot/cereal/log.capnp +++ b/openpilot/cereal/log.capnp @@ -414,6 +414,10 @@ struct CanData { struct DeviceState @0xa4d8b5af2aa492eb { deviceType @45 :InitData.DeviceType; + # usb + chestnutPresent @51 :Bool; + usbState @52 :UsbState; + networkType @22 :NetworkType; networkInfo @31 :NetworkInfo; networkStrength @24 :NetworkStrength; @@ -684,6 +688,20 @@ struct PeripheralState { } } +struct UsbState { + devices @0 :List(Device); + + struct Device { + busnum @0 :UInt8; + devnum @1 :UInt8; + vendorId @2 :UInt16; + productId @3 :UInt16; + speedMbps @4 :UInt16; + manufacturer @6 :Text; + product @5 :Text; + } +} + struct RadarState @0x9a185389d6fdd05f { mdMonoTime @6 :UInt64; carStateMonoTime @11 :UInt64; diff --git a/openpilot/common/hardware/usb.py b/openpilot/common/hardware/usb.py new file mode 100644 index 000000000..805f32407 --- /dev/null +++ b/openpilot/common/hardware/usb.py @@ -0,0 +1,63 @@ +from pathlib import Path + +CHESTNUT_VENDOR_ID = 0xADD1 +CHESTNUT_PRODUCT_ID = 0x0001 +USB_DEVICES_PATH = Path("/sys/bus/usb/devices") + + +def read(path: Path) -> str | None: + try: + return path.read_text().strip() + except OSError: + return None + + +def read_int(path: Path, base: int = 10) -> int: + try: + return int(path.read_text(), base) + except (OSError, ValueError): + return 0 + + +def usb_devices() -> list[Path]: + try: + devices = (d for d in USB_DEVICES_PATH.glob("*") if (d / "idVendor").exists()) + return sorted(devices, key=lambda p: p.name) + except OSError: + return [] + + +def get_usb_state() -> list[dict]: + devices = [] + for device in usb_devices(): + vendor_id = read_int(device / "idVendor", 16) + product_id = read_int(device / "idProduct", 16) + devices.append({ + "busnum": read_int(device / "busnum"), + "devnum": read_int(device / "devnum"), + "vendorId": vendor_id, + "productId": product_id, + "speedMbps": read_int(device / "speed"), + "manufacturer": read(device / "manufacturer") or "", + "product": read(device / "product") or "", + }) + return devices + + +def set_usb_state(device_state, devices: list[dict]) -> None: + entries = device_state.usbState.init('devices', len(devices)) + + chestnut_present = False + for entry, device in zip(entries, devices, strict=True): + entry.busnum = device["busnum"] + entry.devnum = device["devnum"] + entry.vendorId = device["vendorId"] + entry.productId = device["productId"] + entry.speedMbps = device["speedMbps"] + entry.manufacturer = device["manufacturer"] + entry.product = device["product"] + + if (entry.vendorId, entry.productId) == (CHESTNUT_VENDOR_ID, CHESTNUT_PRODUCT_ID): + chestnut_present = True + + device_state.chestnutPresent = chestnut_present diff --git a/openpilot/system/hardware/hardwared.py b/openpilot/system/hardware/hardwared.py index c56c37082..077965e8a 100755 --- a/openpilot/system/hardware/hardwared.py +++ b/openpilot/system/hardware/hardwared.py @@ -18,6 +18,7 @@ from openpilot.common.params import Params from openpilot.common.realtime import DT_HW from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert from openpilot.common.hardware import HARDWARE, TICI, PC +from openpilot.common.hardware.usb import get_usb_state, set_usb_state from openpilot.system.loggerd.config import get_available_percent from openpilot.common.swaglog import cloudlog from openpilot.system.hardware.power_monitoring import PowerMonitoring @@ -36,7 +37,7 @@ ONROAD_CYCLE_TIME = 1 # seconds to wait offroad after requesting an onroad cycl ThermalBand = namedtuple("ThermalBand", ['min_temp', 'max_temp']) HardwareState = namedtuple("HardwareState", ['network_type', 'network_info', 'network_strength', 'network_stats', - 'network_metered', 'modem_temps']) + 'network_metered', 'modem_temps', 'usb_state']) # List of thermal bands. We will stay within this region as long as we are within the bounds. # When exiting the bounds, we'll jump to the lower or higher band. Bands are ordered in the dict. @@ -124,6 +125,7 @@ def hw_state_thread(end_event, hw_queue): network_stats={'wwanTx': tx, 'wwanRx': rx}, network_metered=HARDWARE.get_network_metered(network_type), modem_temps=modem_temps, + usb_state=get_usb_state(), ) try: @@ -166,6 +168,7 @@ def hardware_thread(end_event, hw_queue) -> None: network_strength=NetworkStrength.unknown, network_stats={'wwanTx': -1, 'wwanRx': -1}, modem_temps=[], + usb_state=[], ) all_temp_filter = FirstOrderFilter(0., TEMP_TAU, DT_HW, initialized=False) @@ -246,6 +249,8 @@ def hardware_thread(end_event, hw_queue) -> None: msg.deviceState.screenBrightnessPercent = HARDWARE.get_screen_brightness() + set_usb_state(msg.deviceState, last_hw_state.usb_state) + # this subset is only used for offroad temp_sources = [ msg.deviceState.memoryTempC,