From f174e39d7a799d2523da0650e043973a3389451e Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Thu, 10 Sep 2026 13:19:44 -0700 Subject: [PATCH] add powertest for mici (#38843) --- Jenkinsfile | 1 + .../common/hardware/comma/power_monitor.py | 13 +++++++----- openpilot/selfdrive/test/test_power_draw.py | 21 +++++++++++++++---- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index b6b3eec5e3..2aa522e9d3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -256,6 +256,7 @@ node { deviceStage("chestnut", "mici-chestnut-ci", ["UNSAFE=1", "CHESTNUT=1"], [ step("build", "./openpilot/selfdrive/test/chestnut.sh"), step("model replay", "openpilot/selfdrive/test/process_replay/model_replay.py --chestnut"), + step("test power draw", "./openpilot/selfdrive/test/test_power_draw.py"), ]) }, diff --git a/openpilot/common/hardware/comma/power_monitor.py b/openpilot/common/hardware/comma/power_monitor.py index 296290dae8..9ae2c9e138 100755 --- a/openpilot/common/hardware/comma/power_monitor.py +++ b/openpilot/common/hardware/comma/power_monitor.py @@ -9,22 +9,25 @@ from openpilot.common.realtime import Ratekeeper from openpilot.common.filter_simple import FirstOrderFilter -def read_power(): +def read_power(panda=None): + if panda is not None and panda.get_type() == panda.HW_TYPE_CUATRO: + health = panda.health() + return health['voltage'] * health['current'] / 1e6 with open("/sys/bus/i2c/devices/0-0040/hwmon/hwmon1/power1_input") as f: return int(f.read()) / 1e6 -def sample_power(seconds=5) -> list[float]: +def sample_power(seconds=5, panda=None) -> list[float]: rate = 123 rk = Ratekeeper(rate, print_delay_threshold=None) pwrs = [] for _ in range(rate*seconds): - pwrs.append(read_power()) + pwrs.append(read_power(panda)) rk.keep_time() return pwrs -def get_power(seconds=5): - pwrs = sample_power(seconds) +def get_power(seconds=5, panda=None): + pwrs = sample_power(seconds, panda) return np.mean(pwrs) def wait_for_power(min_pwr, max_pwr, min_secs_in_range, timeout): diff --git a/openpilot/selfdrive/test/test_power_draw.py b/openpilot/selfdrive/test/test_power_draw.py index 1d0be24ef0..d8b234fe60 100755 --- a/openpilot/selfdrive/test/test_power_draw.py +++ b/openpilot/selfdrive/test/test_power_draw.py @@ -5,6 +5,8 @@ import time import unittest import numpy as np from dataclasses import dataclass +from panda import Panda +from openpilot.common.hardware import HARDWARE from openpilot.common.test import OpenpilotTestCase from openpilot.common.utils import tabulate @@ -14,11 +16,14 @@ from opendbc.car.car_helpers import get_demo_car_params from openpilot.common.mock import mock_messages from openpilot.common.params import Params from openpilot.common.hardware.comma.power_monitor import get_power +from openpilot.selfdrive.modeld.helpers import chestnut_present from openpilot.system.manager.process_config import managed_processes from openpilot.system.manager.manager import manager_cleanup SAMPLE_TIME = 2 # seconds to sample power MAX_WARMUP_TIME = 30 # seconds to wait for SAMPLE_TIME consecutive valid samples +MICI = HARDWARE.get_device_type() == "mici" +CHESTNUT = chestnut_present() @dataclass class Proc: @@ -33,9 +38,10 @@ class Proc: return '+'.join(self.procs) +# MICI readings exclude the separately powered Chestnut GPU. PROCS = [ - Proc(['camerad'], 1.65, atol=0.4, msgs=['narrowRoadCameraState', 'wideRoadCameraState', 'cabinCameraState']), - Proc(['modeld'], 1.5, atol=0.2, msgs=['modelV2']), + Proc(['camerad'], 0.85 if MICI else 1.65, atol=0.4, msgs=['narrowRoadCameraState', 'wideRoadCameraState', 'cabinCameraState']), + Proc(['modeld'], 0.45 if MICI and CHESTNUT else 1.5, atol=0.2, msgs=['modelV2']), Proc(['dmonitoringmodeld'], 0.65, atol=0.35, msgs=['driverStateV2']), Proc(['encoderd'], 0.23, msgs=[]), ] @@ -46,6 +52,13 @@ class TestPowerDraw(OpenpilotTestCase): def setup_method(self): Params().put("CarParams", get_demo_car_params().to_bytes(), block=True) + self.panda = None + if MICI: + HARDWARE.reset_internal_panda() + self.addCleanup(HARDWARE.reset_internal_panda) + Panda.wait_for_panda(None, 30) + self.panda = Panda(cli=False) + self.addCleanup(self.panda.close) def teardown_method(self): manager_cleanup() @@ -78,7 +91,7 @@ class TestPowerDraw(OpenpilotTestCase): start_time = time.monotonic() while (time.monotonic() - start_time) < MAX_WARMUP_TIME: - power = get_power(1) + power = get_power(1, self.panda) iteration_msg_counts = {} for msg,sock in socks.items(): iteration_msg_counts[msg] = len(messaging.drain_sock_raw(sock)) @@ -97,7 +110,7 @@ class TestPowerDraw(OpenpilotTestCase): @mock_messages(['deviceMotion']) def test_camera_procs(self, subtests): - baseline = get_power() + baseline = get_power(panda=self.panda) prev = baseline used = {}