Files
StarPilot/openpilot/starpilot/aol/tests/hyundai_cpp_order.py
T

112 lines
5.2 KiB
Python

"""Actual exported native status + actual production C++ negotiate/write/HB/read order."""
import ctypes
import os
import subprocess
class PandadOrder:
def __init__(self, safety, library_path, executable):
self.safety = safety
self.lib = ctypes.CDLL(str(library_path))
self.process = subprocess.Popen([str(executable)], stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
self.heartbeat = False
self.compatible = False
self.axis_session = ''
def status(self):
# Board USB0xd5 initializer reads request first, permission second.
request = int(self.safety.aol_get_request_mask())
permission = int(self.safety.aol_get_permission_mask())
mode = ctypes.c_uint16.in_dll(self.lib, 'current_safety_mode').value
word = ctypes.c_uint16.in_dll(self.lib, 'current_safety_param').value
capability = int(ctypes.c_void_p.in_dll(self.lib, 'aol_policy').value is not None)
return request, permission, mode, word, capability
def exchange(self, line):
assert self.process.poll() is None
self.process.stdin.write(line + '\n')
self.process.stdin.flush()
result = self.process.stdout.readline().strip().split()
assert result, self.process.poll()
return result
def step(self, driven, published, now):
before = self.status()
axis_event = driven['aolAxisState']
axis = axis_event.aolAxisState
intent_event = published['aolIntentWire']
fields = [now, int(axis_event.valid), int(axis.qualified), axis.sessionId, axis.observedMonoTime, axis.validUntilMonoTime,
axis_event.logMonoTime, int(axis.desiredLateral), int(axis.desiredLongitudinal), int(intent_event.valid and published['carState'].valid),
axis.sourceCarStateMonoTime, intent_event.logMonoTime, bytes(intent_event.aolIntentWire).hex(), str(axis.intentProducerSessionId) or '-']
capable, request, retain = map(int, self.exchange('PREP ' + ' '.join(map(str, (*before, *fields)))))
if capable:
self.safety.aol_set_host_request(request)
enabled = int(driven['selfdriveState'].selfdriveState.enabled)
self.heartbeat = bool(int(self.exchange('HB ' + ' '.join(map(str, (*before, enabled, capable))))[0]))
self.safety.set_aol_test_heartbeat(self.heartbeat)
after = self.status()
compatible, session = self.exchange('DONE ' + ' '.join(map(str, (*after, capable))))
self.compatible = bool(int(compatible))
self.axis_session = '' if session == '-' else session
return None, {'before': before, 'plan': (capable, request, retain), 'heartbeat': self.heartbeat,
'after': after, 'session': session}
def close(self):
self.process.stdin.close()
try:
assert self.process.wait(timeout=10) == 0
finally:
self.process.stdout.close()
import hashlib
from pathlib import Path
import re
import pytest
@pytest.fixture(scope='module')
def hkg_pandad_order(tmp_path_factory):
try:
import capnproto
include_dir, library_dir = capnproto.INCLUDE_DIR, capnproto.LIB_DIR
except ModuleNotFoundError:
prefix = Path(os.environ['CAPNP_PREFIX'])
assert (prefix / 'include/capnp/message.h').is_file()
include_dir, library_dir = str(prefix / 'include'), str(prefix / 'lib')
root = Path(__file__).resolve().parents[4]
directory = tmp_path_factory.mktemp('hkg-pandad-order')
original = root / 'openpilot/starpilot/aol/tests/hyundai_pandad_order.cc'
pandad = root / 'openpilot/selfdrive/pandad/pandad.cc'
matched = re.search(r' if \(aol_write_ok\) \{.*?\n \}', pandad.read_text(), re.S)
assert matched is not None
source = directory / 'peer.cc'
source.write_text(original.read_text().replace('// PRODUCTION_HEARTBEAT_BLOCK', matched[0]))
generated = [root / ('openpilot/cereal/gen/cpp/' + name + '.capnp.c++') for name in ('log', 'car', 'custom', 'deprecated')]
paths = [original, pandad, *generated, *list((root / 'openpilot/cereal/gen/cpp').glob('*.capnp.h'))]
roots = [root, root / 'openpilot', root / 'opendbc_repo', root / 'openpilot/cereal/gen/cpp']
closure = {}
queue = [original, *paths[2:]]
while queue:
path = queue.pop()
if path in closure:
continue
closure[path] = hashlib.sha256(path.read_bytes()).hexdigest()
for quoted in re.findall(r'^\s*#include\s+"([^"\n]+)"', path.read_text(), re.M):
included = next((r / quoted for r in [path.parent, *roots] if (r / quoted).is_file()), None)
assert included is not None, (path, quoted)
queue.append(included)
closure[pandad] = hashlib.sha256(pandad.read_bytes()).hexdigest()
executable = directory / 'peer'
subprocess.run(['c++', '-std=c++17', '-O1', '-Wall', '-Wextra', '-Werror',
*[flag for r in roots for flag in ('-I', str(r))], '-I', include_dir,
str(source), *map(str, generated), '-L', library_dir, '-lcapnp', '-lkj', '-o', str(executable)],
check=True, timeout=40)
assert all(hashlib.sha256(path.read_bytes()).hexdigest() == value for path, value in closure.items())
print({'productionHeartbeatSourceClosure': {str(p.relative_to(root)): h for p, h in closure.items()},
'cppArtifactSha256': hashlib.sha256(executable.read_bytes()).hexdigest()})
yield lambda native, library: PandadOrder(native, library, executable)
assert all(hashlib.sha256(path.read_bytes()).hexdigest() == value for path, value in closure.items())