mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-06 20:43:46 +08:00
Restore recorded Elantra diagnostic admission and Panda fault logging
This commit is contained in:
@@ -34,3 +34,6 @@ if arch != "Darwin":
|
||||
|
||||
if GetOption('extras') or any(target.startswith('openpilot/selfdrive/pandad/tests/test_preap_pedal_startup') for target in COMMAND_LINE_TARGETS):
|
||||
env.Program('tests/test_preap_pedal_startup', ['tests/test_preap_pedal_startup.cc', env.Object('tests/preap_panda_safety.o', 'panda_safety.cc')], LIBS=[panda] + libs + ['dl'])
|
||||
|
||||
if GetOption('extras') or any(target.startswith('openpilot/selfdrive/pandad/tests/test_panda_debug') for target in COMMAND_LINE_TARGETS):
|
||||
env.Program('tests/test_panda_debug', ['tests/test_panda_debug.cc'], LIBS=[panda] + libs)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -64,9 +64,13 @@ std::string Panda::serial_read(int port_number) {
|
||||
std::string ret;
|
||||
char buffer[USBPACKET_MAX_SIZE] = {};
|
||||
|
||||
while (true) {
|
||||
int bytes_read = handle->control_read(0xe0, port_number, 0, (unsigned char *)buffer, USBPACKET_MAX_SIZE);
|
||||
if (bytes_read <= 0) {
|
||||
// Firmware exposes one debug ring; bound each drain so CAN work keeps its cadence.
|
||||
(void)port_number;
|
||||
constexpr size_t max_debug_bytes = 1024;
|
||||
while (ret.size() < max_debug_bytes) {
|
||||
const uint16_t length = std::min<size_t>(USBPACKET_MAX_SIZE, max_debug_bytes - ret.size());
|
||||
int bytes_read = handle->control_read(0xb6, 0, 0, (unsigned char *)buffer, length);
|
||||
if (bytes_read <= 0 || bytes_read > length) {
|
||||
break;
|
||||
}
|
||||
ret.append(buffer, bytes_read);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,53 @@
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/tests/native_test.h"
|
||||
#include "selfdrive/pandad/panda.h"
|
||||
|
||||
class DebugHandle : public PandaCommsHandle {
|
||||
public:
|
||||
std::string payload;
|
||||
int calls = 0;
|
||||
int writes = 0;
|
||||
bool endless = false;
|
||||
int error = 0;
|
||||
int control_write(uint8_t, uint16_t, uint16_t, unsigned int) override { ++writes; return 0; }
|
||||
int control_read(uint8_t request, uint16_t p1, uint16_t p2, unsigned char *out, uint16_t length, unsigned int) override {
|
||||
++calls;
|
||||
CHECK(request == 0xb6);
|
||||
CHECK(p1 == 0 && p2 == 0 && length > 0 && length <= 64);
|
||||
if (error) return error;
|
||||
if (endless) { memset(out, 'x', length); return length; }
|
||||
const size_t count = std::min<size_t>(length, payload.size());
|
||||
memcpy(out, payload.data(), count);
|
||||
payload.erase(0, count);
|
||||
return count;
|
||||
}
|
||||
int bulk_write(unsigned char, unsigned char *, int, unsigned int) override { ++writes; return 0; }
|
||||
int bulk_read(unsigned char, unsigned char *, int, unsigned int) override { return 0; }
|
||||
void cleanup() override {}
|
||||
};
|
||||
class DebugPanda : public Panda {
|
||||
public:
|
||||
explicit DebugPanda(std::unique_ptr<DebugHandle> handle) : Panda(std::move(handle), cereal::PandaState::PandaType::CUATRO) {}
|
||||
};
|
||||
void test_debug_transport() {
|
||||
auto handle = std::make_unique<DebugHandle>(); auto *io = handle.get();
|
||||
DebugPanda panda(std::move(handle));
|
||||
io->payload = std::string(83, 'a') + "Temporary fault occurred: 0x00000004\r\n";
|
||||
const std::string expected = io->payload;
|
||||
CHECK(panda.serial_read() == expected);
|
||||
CHECK(io->calls == 3 && io->payload.empty() && io->writes == 0);
|
||||
io->calls = 0; CHECK(panda.serial_read().empty()); CHECK(io->calls == 1);
|
||||
io->calls = 0; io->endless = true;
|
||||
CHECK(panda.serial_read() == std::string(1024, 'x'));
|
||||
CHECK(io->calls == 16 && io->writes == 0);
|
||||
io->endless = false; io->error = -1; io->calls = 0;
|
||||
CHECK(panda.serial_read().empty()); CHECK(io->calls == 1);
|
||||
io->error = 65; io->calls = 0;
|
||||
CHECK(panda.serial_read().empty()); CHECK(io->calls == 1 && io->writes == 0);
|
||||
}
|
||||
int main() { return run_native_test(test_debug_transport); }
|
||||
@@ -21,9 +21,17 @@ def _diagnostic_mask(cp):
|
||||
int(cp.flags) & int(HondaFlags.BOSCH) and qualified(cp, marked_only=True)) else 0
|
||||
if cp.brand == 'hyundai':
|
||||
from opendbc.car.hyundai.values import CAR, HyundaiFlags
|
||||
from opendbc.car.hyundai.classic_scc_aol import qualified
|
||||
if cp.carFingerprint == CAR.HYUNDAI_ELANTRA_2021:
|
||||
# Route08's stock-SCC firmware-matched profile: Dom reports this latched
|
||||
# camera-side CAN1 diagnostic without withdrawing actuator authority.
|
||||
# Keep the raw bit and require the ordinary native/RX/heartbeat guards.
|
||||
recorded_flags = HyundaiFlags.SEND_LFA | HyundaiFlags.USE_FCA | HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HAS_LDA_BUTTON
|
||||
return (1 << 2) if (str(cp.fingerprintSource) == 'fw' and int(cp.flags) == int(recorded_flags) and
|
||||
len(cp.safetyConfigs) == 1 and int(cp.safetyConfigs[0].safetyParam) == 0xC00 and
|
||||
cp.alternativeExperience == 32 and qualified(cp, marked_only=True)) else 0
|
||||
if cp.carFingerprint != CAR.HYUNDAI_SONATA_HYBRID:
|
||||
return 0
|
||||
from opendbc.car.hyundai.classic_scc_aol import qualified
|
||||
recorded_flags = (HyundaiFlags.HYBRID | HyundaiFlags.SEND_LFA | HyundaiFlags.USE_FCA |
|
||||
HyundaiFlags.MANDO_RADAR | HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HAS_LDA_BUTTON)
|
||||
return (1 << 3) if (int(cp.flags) == int(recorded_flags) and
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
"""Elantra route08: retain Dom diagnostic semantics without granting native authority."""
|
||||
from contextlib import ExitStack
|
||||
from dataclasses import replace
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from opendbc.car import gen_empty_fingerprint
|
||||
from opendbc.car.hyundai.classic_scc_aol import aol_word, qualified
|
||||
from opendbc.car.hyundai.interface import CarInterface
|
||||
from opendbc.car.hyundai.values import CAR
|
||||
from openpilot.cereal import log, messaging
|
||||
from openpilot.common.prefix import OpenpilotPrefix
|
||||
from openpilot.starpilot.aol.panda_faults import blocking_fault_list, blocking_fault_mask
|
||||
from openpilot.starpilot.aol.runtime import current_native
|
||||
from openpilot.starpilot.aol.tests import test_ioniq_availability as fixture
|
||||
from openpilot.starpilot.aol.tests.test_ordinary_source_availability import settings
|
||||
|
||||
|
||||
def recorded_cp():
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[0].update({0x391: 8, 0x38d: 8})
|
||||
fingerprint[2][0x485] = 4
|
||||
cp = CarInterface.get_params(CAR.HYUNDAI_ELANTRA_2021, fingerprint, [], False, False, False)
|
||||
cp.fingerprintSource = 'fw'
|
||||
cp.safetyConfigs[0].safetyParam = aol_word(cp)
|
||||
cp.alternativeExperience = 32
|
||||
assert int(cp.flags) == 16843136 and cp.safetyConfigs[0].safetyParam == 0xC00
|
||||
assert qualified(cp, marked_only=True)
|
||||
return cp
|
||||
|
||||
|
||||
@pytest.mark.parametrize('full', [False, True])
|
||||
@pytest.mark.parametrize('fault', ['none', 'rx', 'relay', 'denied', 'request', 'expired-wire'])
|
||||
def test_recorded_irq_through_selfdrived_and_controls_keeps_native_vetoes(full, fault):
|
||||
cp = recorded_cp()
|
||||
before = cp.to_dict()
|
||||
case = fixture.IoniqAvailabilityTests(methodName='runTest')
|
||||
with ExitStack() as cleanup:
|
||||
cleanup.callback(case.doCleanups)
|
||||
cleanup.enter_context(OpenpilotPrefix())
|
||||
sd, controls, drive = case._authority_callers(full, cp=cp, genuine_events=True)
|
||||
new_message = messaging.new_message
|
||||
def with_irq(service=None, size=None, **kwargs):
|
||||
message = new_message(service, size, **kwargs)
|
||||
if service == 'pandaStates':
|
||||
for panda in message.pandaStates:
|
||||
panda.faults = ['interruptRateCan1']
|
||||
return message
|
||||
encode = fixture.encode_safety
|
||||
def wire_irq(state):
|
||||
state = replace(state, pandaInventory=tuple(replace(slot, faults=slot.faults | 4) for slot in state.pandaInventory))
|
||||
if fault == 'expired-wire':
|
||||
state = replace(state, observedMonoTime=state.observedMonoTime - 300_000_000,
|
||||
validUntilMonoTime=state.validUntilMonoTime - 300_000_000)
|
||||
return encode(state)
|
||||
cleanup.enter_context(mock.patch.object(messaging, 'new_message', with_irq))
|
||||
cleanup.enter_context(mock.patch.object(fixture, 'encode_safety', wire_irq))
|
||||
for tick in range(3):
|
||||
stamp = 10_000_000_000 + tick * 10_000_000
|
||||
options = settings('combined' if full else 'lateral_only', stamp, tick + 1)
|
||||
observed = fixture.car_state()
|
||||
observed.cruiseState.enabled = full
|
||||
options['observed_state'] = observed
|
||||
options.update(panda_fault=fault == 'rx', relay_fault=fault == 'relay', long_allowed=full)
|
||||
if fault == 'denied':
|
||||
options.update(allowed=False, long_allowed=False)
|
||||
if fault == 'request':
|
||||
options.update(native_request_lat=False, native_request_long=False)
|
||||
axis, state, command = drive(tick, **options)
|
||||
if fault == 'none':
|
||||
assert axis.lateralActive and command.latActive, 'RECORDED_ELANTRA_IRQ_DIAGNOSTIC_REJECTED'
|
||||
assert log.OnroadEvent.EventName.controlsMismatch not in sd.events.names
|
||||
native = current_native(sd.sm, sd.CP, now_ns=stamp, axis_session_id=sd.aol_session_id)
|
||||
assert native is not None and native.pandaInventory[0].faults == 4
|
||||
assert list(sd.sm['pandaStates'][0].faults) == [log.PandaState.FaultType.interruptRateCan1]
|
||||
assert not axis.longitudinalActive and not command.longActive
|
||||
assert state.enabled == full
|
||||
else:
|
||||
assert not axis.lateralActive and not command.latActive
|
||||
assert not axis.longitudinalActive and not command.longActive
|
||||
assert cp.to_dict() == before
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name,value', tuple(log.PandaState.FaultType.schema.enumerants.items()))
|
||||
def test_other_and_mixed_faults_still_block(name, value):
|
||||
cp = recorded_cp()
|
||||
expected = name != 'interruptRateCan1'
|
||||
assert blocking_fault_mask(1 << value, cp) == expected
|
||||
assert blocking_fault_mask((1 << value) | 4, cp) == expected
|
||||
assert blocking_fault_list([value], cp) == expected
|
||||
assert blocking_fault_list([log.PandaState.FaultType.interruptRateCan1, value], cp) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('mutation', ['alpha', 'fixed', 'can', 'word', 'unmarked', 'flags', 'identity', 'extra', 'passive'])
|
||||
def test_unrecorded_configurations_cannot_borrow_diagnostic(mutation):
|
||||
cp = recorded_cp()
|
||||
if mutation == 'alpha':
|
||||
cp.openpilotLongitudinalControl = True
|
||||
cp.pcmCruise = False
|
||||
elif mutation in ('fixed', 'can'):
|
||||
cp.fingerprintSource = mutation
|
||||
elif mutation == 'word':
|
||||
cp.safetyConfigs[0].safetyParam |= 4
|
||||
elif mutation == 'unmarked':
|
||||
cp.alternativeExperience = 0
|
||||
elif mutation == 'flags':
|
||||
cp.flags |= 1 << 31
|
||||
elif mutation == 'identity':
|
||||
cp.carFingerprint = CAR.HYUNDAI_SONATA
|
||||
elif mutation == 'extra':
|
||||
cp.safetyConfigs = [cp.safetyConfigs[0], cp.safetyConfigs[0]]
|
||||
else:
|
||||
cp.passive = True
|
||||
assert blocking_fault_mask(4, cp)
|
||||
assert blocking_fault_list([log.PandaState.FaultType.interruptRateCan1], cp)
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"protected_inventory_sha256": "c822f9b15f3f13ee17c2a7c0ceb3062caffb81ffebab07965943044cc00c01f2", "schema_version": 1}
|
||||
{"protected_inventory_sha256": "85f436188fc0877fc0e2da301331a3c74e2d068985c5f1c5f962c9b3bf541deb", "schema_version": 1}
|
||||
|
||||
Reference in New Issue
Block a user