From 7db3785389a335990bb50936756dcd9e30bdb189 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 5 Dec 2025 15:03:36 -0800 Subject: [PATCH 1/9] it's just unsupported --- python/__init__.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/python/__init__.py b/python/__init__.py index 0f18a523..5f14376b 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -112,8 +112,6 @@ class Panda: # from https://github.com/commaai/openpilot/blob/103b4df18cbc38f4129555ab8b15824d1a672bdf/cereal/log.capnp#L648 HW_TYPE_UNKNOWN = b'\x00' - HW_TYPE_WHITE = b'\x01' - HW_TYPE_BLACK = b'\x03' HW_TYPE_RED_PANDA = b'\x07' HW_TYPE_TRES = b'\x09' HW_TYPE_CUATRO = b'\x0a' @@ -148,9 +146,9 @@ class Panda: self._can_speed_kbps = can_speed_kbps if cli and serial is None: - self._connect_serial = self._cli_select_panda() + self._connect_serial = self._cli_select_panda() else: - self._connect_serial = serial + self._connect_serial = serial # connect and set mcu type self.connect(claim) @@ -214,8 +212,7 @@ class Panda: logger.debug("connected") hw_type = self.get_type() - if hw_type not in self.SUPPORTED_DEVICES: - print("WARNING: Using deprecated HW") + assert hw_type in self.SUPPORTED_DEVICES, f"Unknown HW: {hw_type}" # disable openpilot's heartbeat checks if self._disable_checks: From 5b3ada1e074f11820e77fbdc9aab7bb5c458e1d7 Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Mon, 8 Dec 2025 14:00:46 +0100 Subject: [PATCH 2/9] cleanup fan scripts --- scripts/fan/fan_test.py | 2 +- scripts/fan/fan_tuning.py | 85 --------------------------------------- 2 files changed, 1 insertion(+), 86 deletions(-) delete mode 100755 scripts/fan/fan_tuning.py diff --git a/scripts/fan/fan_test.py b/scripts/fan/fan_test.py index 36a11715..a3872f5c 100755 --- a/scripts/fan/fan_test.py +++ b/scripts/fan/fan_test.py @@ -9,6 +9,6 @@ if __name__ == "__main__": while True: p.set_fan_power(power) time.sleep(5) - print("Power: ", power, "RPM:", str(p.get_fan_rpm()), "Expected:", int(6500 * power / 100)) + print("Power: ", power, "RPM:", str(p.get_fan_rpm())) power += 10 power %= 110 diff --git a/scripts/fan/fan_tuning.py b/scripts/fan/fan_tuning.py deleted file mode 100755 index 60ec72ec..00000000 --- a/scripts/fan/fan_tuning.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/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 - 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 = (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(), - } - 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() From f373c69bbb3d1068f1dfc1323cd2fa37e974c389 Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:00:20 -0500 Subject: [PATCH 3/9] garbage collect always-true condition check (#2305) garbage collect dead code --- board/drivers/usb.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/board/drivers/usb.h b/board/drivers/usb.h index 1f715fee..d393d7e4 100644 --- a/board/drivers/usb.h +++ b/board/drivers/usb.h @@ -502,11 +502,8 @@ static void usb_setup(void) { control_req.length = setup.b.wLength.w; resp_len = comms_control_handler(&control_req, response); - // response pending if -1 was returned - if (resp_len != -1) { - USB_WritePacket(response, MIN(resp_len, setup.b.wLength.w), 0); - USBx_OUTEP(0U)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK; - } + USB_WritePacket(response, MIN(resp_len, setup.b.wLength.w), 0); + USBx_OUTEP(0U)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK; } } From d72c8b4e8e9bf1d267576610a6c8f5c1b8cbb8b5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 20 Dec 2025 14:19:09 -0800 Subject: [PATCH 4/9] CI: use tags for cppcheck update cppcheck doesn't always create a release for each tag --- .github/workflows/update-cppcheck.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/update-cppcheck.yml b/.github/workflows/update-cppcheck.yml index 6a2816fb..da58d09c 100644 --- a/.github/workflows/update-cppcheck.yml +++ b/.github/workflows/update-cppcheck.yml @@ -17,7 +17,11 @@ jobs: - name: Get latest cppcheck version id: version run: | - LATEST=$(curl -fsSL https://api.github.com/repos/danmar/cppcheck/releases/latest | jq -r .tag_name) + # Tags are sorted by time (newest first), so get the first version-like tag + LATEST=$(curl -fsSL "https://api.github.com/repos/danmar/cppcheck/tags?per_page=20" | \ + jq -r '.[].name' | \ + grep -E '^[0-9]+\.[0-9]+(\.[0-9]+)?$' | \ + head -n 1) echo "vers=$LATEST" >> "$GITHUB_OUTPUT" - name: Update VERS in install.sh run: | From e5074a68555bfd72dff75d26da47f55e31b27bcb Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 20 Dec 2025 14:21:25 -0800 Subject: [PATCH 5/9] lil more --- .github/workflows/update-cppcheck.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/update-cppcheck.yml b/.github/workflows/update-cppcheck.yml index da58d09c..5fd4a51e 100644 --- a/.github/workflows/update-cppcheck.yml +++ b/.github/workflows/update-cppcheck.yml @@ -17,11 +17,11 @@ jobs: - name: Get latest cppcheck version id: version run: | - # Tags are sorted by time (newest first), so get the first version-like tag - LATEST=$(curl -fsSL "https://api.github.com/repos/danmar/cppcheck/tags?per_page=20" | \ - jq -r '.[].name' | \ - grep -E '^[0-9]+\.[0-9]+(\.[0-9]+)?$' | \ - head -n 1) + # Tags are sorted by time (newest first), so get the first version-like tag + LATEST=$(curl -fsSL "https://api.github.com/repos/danmar/cppcheck/tags?per_page=20" | \ + jq -r '.[].name' | \ + grep -E '^[0-9]+\.[0-9]+(\.[0-9]+)?$' | \ + head -n 1) echo "vers=$LATEST" >> "$GITHUB_OUTPUT" - name: Update VERS in install.sh run: | From e42367df97f1a5ea4ddb152566022c3ae4672e58 Mon Sep 17 00:00:00 2001 From: downquark7 Date: Sun, 28 Dec 2025 14:53:02 -0600 Subject: [PATCH 6/9] Adjust `gitversion` handling to include null terminator in length calculations. (#2309) Co-authored-by: Adeeb Shihadeh --- SConscript | 4 ++-- board/flasher.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SConscript b/SConscript index d03a2104..957982ca 100644 --- a/SConscript +++ b/SConscript @@ -139,8 +139,8 @@ base_project_h7 = { # Common autogenerated includes with open("board/obj/gitversion.h", "w") as f: version = get_version(BUILDER, BUILD_TYPE) - f.write(f'extern const uint8_t gitversion[{len(version)}];\n') - f.write(f'const uint8_t gitversion[{len(version)}] = "{version}";\n') + f.write(f'extern const uint8_t gitversion[{len(version)+1}];\n') + f.write(f'const uint8_t gitversion[{len(version)+1}] = "{version}";\n') with open("board/obj/version", "w") as f: f.write(f'{get_version(BUILDER, BUILD_TYPE)}') diff --git a/board/flasher.h b/board/flasher.h index 34d93520..f816abec 100644 --- a/board/flasher.h +++ b/board/flasher.h @@ -83,7 +83,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { case 0xd6: COMPILE_TIME_ASSERT(sizeof(gitversion) <= USBPACKET_MAX_SIZE); memcpy(resp, gitversion, sizeof(gitversion)); - resp_len = sizeof(gitversion); + resp_len = sizeof(gitversion) - 1U; break; // **** 0xd8: reset ST case 0xd8: From ce1b6a67e4fff7bf5228ff7239152ad0a4498c55 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 12 Jan 2026 15:59:49 -0800 Subject: [PATCH 7/9] fix up fan HITL test (#2317) * fix up fan HITL test * cleanup * times two * even simpler * append * one more... * speed it up now * oops --- python/__init__.py | 5 ----- tests/hitl/7_internal.py | 24 +++++++++++++----------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/python/__init__.py b/python/__init__.py index 5f14376b..95d47d66 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -128,11 +128,6 @@ class Panda: INTERNAL_DEVICES = (HW_TYPE_TRES, HW_TYPE_CUATRO) - MAX_FAN_RPMs = { - HW_TYPE_TRES: 6600, - HW_TYPE_CUATRO: 5000, - } - HARNESS_STATUS_NC = 0 HARNESS_STATUS_NORMAL = 1 HARNESS_STATUS_FLIPPED = 2 diff --git a/tests/hitl/7_internal.py b/tests/hitl/7_internal.py index 47c24a47..a7012ebc 100644 --- a/tests/hitl/7_internal.py +++ b/tests/hitl/7_internal.py @@ -5,28 +5,30 @@ from panda import Panda pytestmark = [ pytest.mark.test_panda_types(Panda.INTERNAL_DEVICES), - pytest.mark.test_panda_types([Panda.HW_TYPE_TRES]) ] +MAX_RPM = 5000 + @pytest.mark.timeout(2*60) def test_fan_curve(p): # ensure fan curve is (roughly) linear - 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, then wait a bit more for the RPM to converge + rpms = [] + for power in (30, 70, 100): + # wait until fan spins up p.set_fan_power(power) for _ in range(20): time.sleep(1) if p.get_fan_rpm() > 1000: break - time.sleep(5) + time.sleep(2) # wait for RPM to converge + rpms.append(p.get_fan_rpm()) + + print(rpms) + diffs = [b - a for a, b in zip(rpms, rpms[1:])] + assert all(x > 0 for x in diffs), f"Fan RPMs not strictly increasing: {rpms=}" + assert rpms[-1] > (0.75*MAX_RPM) - expected_rpm = Panda.MAX_FAN_RPMs[bytes(p.get_type())] * power / 100 - assert 0.75 * expected_rpm <= p.get_fan_rpm() <= 1.25 * expected_rpm def test_fan_cooldown(p): # if the fan cooldown doesn't work, we get high frequency noise on the tach line @@ -35,5 +37,5 @@ def test_fan_cooldown(p): time.sleep(3) p.set_fan_power(0) for _ in range(5): - assert p.get_fan_rpm() <= Panda.MAX_FAN_RPMs[bytes(p.get_type())] + assert p.get_fan_rpm() <= MAX_RPM*2 time.sleep(0.5) From 3dd38b76b48903efb4705f55752e9719ba2f5564 Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Thu, 15 Jan 2026 00:49:43 +0800 Subject: [PATCH 8/9] Build everything before jungle recover (#2316) * make sure everything is built, including the bootloader * also for flash --- board/jungle/flash.py | 2 +- board/jungle/recover.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/board/jungle/flash.py b/board/jungle/flash.py index 8378169e..6f60f70b 100755 --- a/board/jungle/flash.py +++ b/board/jungle/flash.py @@ -12,7 +12,7 @@ if __name__ == "__main__": parser.add_argument("--all", action="store_true", help="Recover all panda jungle devices") args = parser.parse_args() - subprocess.check_call(f"scons -C {board_path}/.. -u -j$(nproc) {board_path}", shell=True) + subprocess.check_call(f"scons -C {board_path}/.. -u -j$(nproc) .", shell=True) if args.all: serials = PandaJungle.list() diff --git a/board/jungle/recover.py b/board/jungle/recover.py index 34c88e26..228f0c56 100755 --- a/board/jungle/recover.py +++ b/board/jungle/recover.py @@ -13,7 +13,7 @@ if __name__ == "__main__": parser.add_argument("--all", action="store_true", help="Recover all panda jungle devices") args = parser.parse_args() - subprocess.check_call(f"scons -C {board_path}/.. -u -j$(nproc) {board_path}", shell=True) + subprocess.check_call(f"scons -C {board_path}/.. -u -j$(nproc) .", shell=True) serials = PandaJungle.list() if args.all else [None] for s in serials: From 94c60823a47e505cd3c86336558dc5af30dabc06 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 19 Jan 2026 21:20:30 -0500 Subject: [PATCH 9/9] Revert "it's just unsupported" This reverts commit 7db3785389a335990bb50936756dcd9e30bdb189. --- python/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python/__init__.py b/python/__init__.py index c8f83e5b..7c29bc5b 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -112,6 +112,8 @@ class Panda: # from https://github.com/commaai/openpilot/blob/103b4df18cbc38f4129555ab8b15824d1a672bdf/cereal/log.capnp#L648 HW_TYPE_UNKNOWN = b'\x00' + HW_TYPE_WHITE = b'\x01' + HW_TYPE_BLACK = b'\x03' HW_TYPE_RED_PANDA = b'\x07' HW_TYPE_TRES = b'\x09' HW_TYPE_CUATRO = b'\x0a' @@ -208,7 +210,8 @@ class Panda: logger.debug("connected") hw_type = self.get_type() - assert hw_type in self.SUPPORTED_DEVICES, f"Unknown HW: {hw_type}" + if hw_type not in self.SUPPORTED_DEVICES: + print("WARNING: Using deprecated HW") # disable openpilot's heartbeat checks if self._disable_checks: