mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-28 00:53:45 +08:00
c1aeddbfea
Manage device power, parked services and vehicle-specific Panda wake behavior. Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com> Co-authored-by: Daniel Koepping <elkoled@gmail.com> Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>
134 lines
6.3 KiB
Python
134 lines
6.3 KiB
Python
import ast
|
|
import binascii
|
|
import importlib.util
|
|
from itertools import accumulate
|
|
import os
|
|
from pathlib import Path
|
|
import struct
|
|
from types import SimpleNamespace
|
|
from unittest.mock import Mock
|
|
|
|
import pytest
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
|
|
def constants():
|
|
spec = importlib.util.spec_from_file_location('tested_panda_constants', ROOT / 'panda/python/constants.py')
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def production_panda(tmp_path):
|
|
tree = ast.parse((ROOT / 'panda/python/__init__.py').read_text())
|
|
original = next(node for node in tree.body if isinstance(node, ast.ClassDef) and node.name == 'Panda')
|
|
selected = {'get_mcu_type', 'is_internal', 'get_dfu_serial', 'up_to_date', 'flash', 'flash_static'}
|
|
body = [node for node in original.body if isinstance(node, ast.Assign) or isinstance(node, ast.FunctionDef) and node.name in selected]
|
|
cls = ast.ClassDef(name='Panda', bases=[], keywords=[], body=body, decorator_list=[])
|
|
mcu = constants().McuType
|
|
env = {'accumulate': accumulate, 'McuType': mcu, 'compute_version_hash': lambda _: 1, 'opendbc': SimpleNamespace(INCLUDE_PATH=''),
|
|
'os': os, 'BASEDIR': str(tmp_path), '_parse_c_struct': lambda *args: None, 'struct': struct,
|
|
'FW_PATH': str(tmp_path), 'logger': Mock(), 'PandaDFU': Mock(),
|
|
'usb1': SimpleNamespace(ENDPOINT_IN=1, TYPE_VENDOR=2, RECIPIENT_DEVICE=3, ENDPOINT_OUT=4)}
|
|
exec(compile(ast.fix_missing_locations(ast.Module(body=[cls], type_ignores=[])), '<current-panda>', 'exec'), env)
|
|
return env['Panda'], env, mcu
|
|
|
|
|
|
@pytest.mark.parametrize('hardware,expected', [(b'\x06', 'F4'), (b'\x07', 'H7'), (b'\x09', 'H7'), (b'\x0a', 'H7')])
|
|
def test_detected_hardware_selects_exact_firmware_and_dfu_config(tmp_path, hardware, expected):
|
|
cls, env, mcu = production_panda(tmp_path)
|
|
panda = cls()
|
|
panda.get_type = lambda: hardware
|
|
panda._serial = '010002000300040005000600'
|
|
assert panda.get_mcu_type() == getattr(mcu, expected)
|
|
panda.get_dfu_serial()
|
|
env['PandaDFU'].st_serial_to_dfu_serial.assert_called_once_with(panda._serial, getattr(mcu, expected))
|
|
assert panda.is_internal() == (hardware != b'\x07')
|
|
|
|
|
|
@pytest.mark.parametrize('hardware', [b'\x00', b'\x03', b'\xff'])
|
|
def test_unknown_hardware_never_defaults_to_h7(tmp_path, hardware):
|
|
cls, _, _ = production_panda(tmp_path)
|
|
panda = cls()
|
|
panda.get_type = lambda: hardware
|
|
with pytest.raises(ValueError, match='Unknown HW'):
|
|
panda.get_mcu_type()
|
|
|
|
|
|
@pytest.mark.parametrize('hardware', [b'\x06', b'\x09'])
|
|
def test_default_flash_uses_detected_mcu_image_and_static_flash_target(tmp_path, hardware):
|
|
cls, env, _ = production_panda(tmp_path)
|
|
panda = cls()
|
|
panda.get_type = lambda: hardware
|
|
target = panda.get_mcu_type()
|
|
image = tmp_path / target.config.app_fn
|
|
image.write_bytes(b'exact configured image')
|
|
panda.up_to_date = lambda **kwargs: False
|
|
panda.bootstub = True
|
|
panda.get_version = lambda: 'bootstub'
|
|
panda._handle = object()
|
|
panda.reconnect = Mock()
|
|
cls.flash_static = Mock()
|
|
panda.flash()
|
|
cls.flash_static.assert_called_once_with(panda._handle, image.read_bytes(), mcu_type=target)
|
|
panda.reconnect.assert_called_once()
|
|
captured = []
|
|
cls.get_signature_from_firmware = lambda path: captured.append(path) or b'correct'
|
|
panda.get_signature = lambda: b'correct'
|
|
assert cls.up_to_date(panda)
|
|
assert captured == [str(image)]
|
|
|
|
|
|
def test_pandad_signature_binds_detected_mcu_and_rejects_wrong_packet_hashes(tmp_path):
|
|
tree = ast.parse((ROOT / 'openpilot/selfdrive/pandad/pandad.py').read_text())
|
|
functions = [node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name in {'get_expected_signature', 'flash_panda'}]
|
|
mcu = constants().McuType
|
|
for target in (mcu.F4, mcu.H7):
|
|
for versions in ((11, 22), (0, 0), (11, 23)):
|
|
device = SimpleNamespace(get_mcu_type=lambda: target, is_internal=lambda: True, bootstub=False,
|
|
get_version=lambda: 'current', get_signature=lambda: b'correct',
|
|
get_packets_versions=lambda: versions, close=Mock(), flash=Mock())
|
|
factory = Mock(return_value=device)
|
|
factory.HEALTH_PACKET_VERSION, factory.CAN_PACKET_VERSION = 11, 22
|
|
factory.get_signature_from_firmware = Mock(return_value=b'correct')
|
|
class Mismatch(Exception):
|
|
pass
|
|
env = {'os': os, 'FW_PATH': str(tmp_path), 'McuType': mcu, 'Panda': factory,
|
|
'PandaProtocolMismatch': Mismatch, 'cloudlog': Mock(), 'HARDWARE': Mock()}
|
|
exec(compile(ast.fix_missing_locations(ast.Module(body=functions, type_ignores=[])), '<current-wrapper>', 'exec'), env)
|
|
if versions == (11, 22):
|
|
env['flash_panda']('serial')
|
|
else:
|
|
with pytest.raises(Mismatch, match='protocol hash mismatch'):
|
|
env['flash_panda']('serial')
|
|
factory.get_signature_from_firmware.assert_called_once_with(str(tmp_path / target.config.app_fn))
|
|
device.flash.assert_not_called()
|
|
device.close.assert_called_once()
|
|
|
|
|
|
def test_dfu_serial_uses_f4_rom_offset_and_h7_unchanged():
|
|
tree = ast.parse((ROOT / 'panda/python/dfu.py').read_text())
|
|
original = next(node for node in tree.body if isinstance(node, ast.ClassDef) and node.name == 'PandaDFU')
|
|
method = next(node for node in original.body if isinstance(node, ast.FunctionDef) and node.name == 'st_serial_to_dfu_serial')
|
|
cls = ast.ClassDef(name='PandaDFU', bases=[], keywords=[], body=[method], decorator_list=[])
|
|
mcu = constants().McuType
|
|
env = {'McuType': mcu, 'binascii': binascii, 'struct': struct}
|
|
exec(compile(ast.fix_missing_locations(ast.Module(body=[cls], type_ignores=[])), '<current-dfu>', 'exec'), env)
|
|
convert = env['PandaDFU'].st_serial_to_dfu_serial
|
|
assert convert('010002000300040005000600', mcu.F4) == '000800100004'
|
|
assert convert('010002000300040005000600', mcu.H7) == '000800060004'
|
|
assert convert(None, mcu.F4) is None
|
|
|
|
|
|
def test_f4_sector12_is_rejected_before_any_erase_or_write(tmp_path):
|
|
cls, _, mcu = production_panda(tmp_path)
|
|
cls.flasher_present = lambda handle: True
|
|
handle = SimpleNamespace(controlWrite=Mock(), bulkWrite=Mock())
|
|
code = bytes(sum(mcu.F4.config.sector_sizes[1:12]))
|
|
with pytest.raises(AssertionError, match='DOS bootstubs support sectors 1..11'):
|
|
cls.flash_static(handle, code, mcu.F4)
|
|
handle.controlWrite.assert_not_called()
|
|
handle.bulkWrite.assert_not_called()
|