Tesla: clarify Galaxy-only validation

Keep the wake-on-CAN test documentation aligned with the Galaxy-only integration.

Co-authored-by: AngusBell97 <124716116+AngusBell97@users.noreply.github.com>
This commit is contained in:
AngusBell97
2026-09-10 13:53:18 -05:00
committed by firestar5683
parent 9832c3de4f
commit 58d2b6838d
4 changed files with 32 additions and 50 deletions
+2 -2
View File
@@ -55,8 +55,8 @@ lacks the extra non-OFF wake; optional `--regression-ref REV` can test a histori
version that masked the DRIVE edge if available locally.
Host tests cover Tesla detection, stale identity, firmware-selection combinations,
missing files and normal update/signature behaviour. Galaxy/native settings tests
cover Tesla-only visibility and capability checks, parked-only writes, required
missing files and normal update/signature behaviour. Galaxy settings tests cover
Tesla-only visibility and capability checks, parked-only writes, required
confirmation, cancellation and firmware preflight. `host_policy.py` exercises
actual shutdown methods with synthetic clocks and Params, without device access.
+1 -5
View File
@@ -34,7 +34,6 @@ def main():
pm = SimpleNamespace(low_voltage_start_time=None,car_voltage_mV=14000,car_battery_capacity_uWh=30e6,
params=SimpleNamespace(get_bool=lambda name:values[name]))
toggles = SimpleNamespace(device_shutdown_time=3600,low_voltage_shutdown=11.8)
# Wake is deliberately independent, but not a supported input to this policy.
pm.wake_on_can = True
reason = lambda: ns['shutdown_reason'](pm,False,True,1.,False,toggles)
observed = {}
@@ -44,12 +43,9 @@ def main():
if result and 'first_shutdown' not in observed:
observed['first_shutdown']={'monotonic_s':t,'offroad_s':t-1,'reason':result,'fresh_awake':True}
assert observed['first_shutdown']=={'monotonic_s':3602,'offroad_s':3601,'reason':'offroad_timeout','fresh_awake':True}
# The one-hour constant is NOT an unconditional one-hour timeout.
toggles.device_shutdown_time=0
assert reason() is None
observed['no_timeout_healthy_after_hour']=reason()
# Existing emergency/protective inputs stay intact; never bypass by changing ignition
# or setting DisablePowerDown to implement stay-awake.
pm.car_battery_capacity_uWh=0
assert reason()=='battery_capacity_exhausted'
observed['exhausted']=reason()
@@ -64,7 +60,7 @@ def main():
toggles.device_shutdown_time=3600
for awake in [True,False]:
pm.wake_on_can=awake
assert reason()=='offroad_timeout' # same answer; no telemetry seam exists yet
assert reason()=='offroad_timeout'
observed['contract']='Wake-only CAN deliberately does not inhibit stock host shutdown.'
observed['source_sha256']=hashlib.sha256(path.read_bytes()).hexdigest()
observed['thermal']='Not modeled here; unchanged host thermal protection must be independently preserved in any extension.'
-2
View File
@@ -27,11 +27,9 @@ def between(text, start, end):
def generate(read):
can, main, boot, cuatro = [read(p) for p in FILES]
independent_wake = 'bool recent_heartbeat, bool wake)' in boot
# Historical two-argument definitions must not include today's declaration.
if not independent_wake:
boot = boot.replace('#include "bootkick_declarations.h"', '')
globals_ = between(can, 'bool ignition_can = false;', '\nbool can_silent')
# Added wake globals immediately precede ignition globals in candidate.
if 'bool wake_on_can = false;' in can:
globals_ = between(can, 'bool wake_on_can = false;', '\nbool can_silent')
else:
+29 -41
View File
@@ -1,10 +1,8 @@
// Included after verbatim production functions. Each scenario gets a fresh process.
static void tick(void) { timer.SR = 1U; tick_handler(); }
static void second(void) { for (int i=0; i<8; i++) tick(); }
static CANPacket_t packet(unsigned bus, unsigned addr, unsigned dlc, unsigned state, unsigned counter) {
CANPacket_t p = {0}; p.bus=bus; p.addr=addr; p.data_len_code=dlc;
p.data[0]=state<<5; p.data[6]=counter<<4;
// Tesla DBC checksum: address bytes plus seven payload bytes, modulo 256.
unsigned sum=(addr & 255U)+(addr >> 8U);
for (unsigned i=0; i<7; i++) sum+=p.data[i];
p.data[7]=sum & 255U;
@@ -16,13 +14,13 @@ static void tesla(unsigned state, unsigned counter) {
static void pair(unsigned state) { tesla(state,14); tesla(state,15); }
static void wake_case(void) {
assert(!wake_on_can && !ignition_can);
tesla(2,14); assert(!wake_on_can && !ignition_can); // prime only
tesla(2,14); assert(!wake_on_can && !ignition_can);
for (unsigned state=0; state<4; state++) {
tesla(state,(15+state)%16);
assert(wake_on_can == (state != 0)); assert(ignition_can == (state == 3));
assert(wake_on_can_cnt == 0 && ignition_can_cnt == 0);
}
tesla(0,3); assert(!wake_on_can && !ignition_can); // accepted OFF clears immediately
tesla(0,3); assert(!wake_on_can && !ignition_can);
}
static void invalid_case(void) {
pair(2); assert(wake_on_can); wake_on_can_cnt=2;
@@ -37,32 +35,31 @@ static void invalid_case(void) {
}
CANPacket_t p=packet(0,0x220,8,0,0); ignition_can_hook(&p);
assert(wake_on_can && wake_on_can_cnt==2);
tesla(0,15); assert(wake_on_can && wake_on_can_cnt==2); // duplicate
tesla(0,7); assert(wake_on_can && wake_on_can_cnt==2); // jump
tesla(0,8); assert(!wake_on_can && wake_on_can_cnt==0); // reacquire after jump
tesla(0,15); assert(wake_on_can && wake_on_can_cnt==2);
tesla(0,7); assert(wake_on_can && wake_on_can_cnt==2);
tesla(0,8); assert(!wake_on_can && wake_on_can_cnt==0);
tesla(2,9); assert(wake_on_can);
wake_on_can_cnt=2;
p=packet(0,0x221,8,2,10); p.data[7]^=1; ignition_can_hook(&p);
assert(wake_on_can && wake_on_can_cnt==2); // invalid frames cannot refresh wake
tesla(0,11); assert(wake_on_can && wake_on_can_cnt==2); // valid frame primes again
assert(wake_on_can && wake_on_can_cnt==2);
tesla(0,11); assert(wake_on_can && wake_on_can_cnt==2);
tesla(0,12); assert(!wake_on_can && wake_on_can_cnt==0);
}
static void disabled_case(void) {
for (unsigned state=0; state<4; state++) {
pair(state);
assert(!wake_on_can); // default and other manufacturers never gain Tesla wake
assert(ignition_can == (state==3)); // stock DRIVE remains independent of opt-in
assert(!wake_on_can);
assert(ignition_can == (state==3));
}
}
static void checksum_case(void) {
CANPacket_t p=packet(0,0x221,8,2,14);
assert(p.data[7]==0x43); // address 0x23 + ACCESSORY 0x40 + counter 0xE0
assert(p.data[7]==0x43);
p.data[7]^=1; ignition_can_hook(&p);
tesla(2,15); assert(!wake_on_can); // bad checksum cannot prime a wake
tesla(2,0); assert(wake_on_can); // two valid consecutive frames, including wrap
tesla(2,15); assert(!wake_on_can);
tesla(2,0); assert(wake_on_can);
tesla(0,1); assert(!wake_on_can);
// Corruption in any payload/checksum byte cannot create a wake or prime it.
for (unsigned i=0; i<8; i++) {
p=packet(0,0x221,8,2,2); p.data[i]^=1; ignition_can_hook(&p);
assert(!wake_on_can);
@@ -78,8 +75,7 @@ static void checksum_case(void) {
p=packet(0,0x221,8,2,8+i); p.data[7]^=1; ignition_can_hook(&p);
second();
}
assert(!wake_on_can); // checksum-invalid traffic ages out
// Do not change the stock DRIVE decoder while hardening the extra wake path.
assert(!wake_on_can);
p=packet(0,0x221,8,3,12); p.data[7]^=1; ignition_can_hook(&p);
assert(ignition_can && !wake_on_can);
}
@@ -88,9 +84,8 @@ static void stale_case(void) {
pair(2);
for (unsigned i=1; i<=3; i++) { second(); assert(wake_on_can && wake_on_can_cnt==i); }
second(); assert(!wake_on_can && wake_on_can_cnt==4);
// Staleness does not reset the decoder's static previous counter (reference parity).
tesla(2,15); assert(!wake_on_can && wake_on_can_cnt==4);
tesla(2,0); assert(wake_on_can && wake_on_can_cnt==0); // wrap after stale
tesla(2,0); assert(wake_on_can && wake_on_can_cnt==0);
second(); assert(wake_on_can_cnt==1);
tesla(0,1); assert(!wake_on_can && wake_on_can_cnt==0);
wake_on_can_cnt=UINT32_MAX; second(); assert(!wake_on_can && wake_on_can_cnt==0);
@@ -113,19 +108,19 @@ static void drive_watchdog_case(void) {
tesla(3,4); second(); assert(heartbeat_counter==5 && heartbeat_lost);
}
static void boot_case(void) {
legacy_bootkick(false,false); assert(observed_boot==BOOT_BOOTKICK); // first power-on
legacy_bootkick(false,false); assert(observed_boot==BOOT_BOOTKICK);
for (int i=0; i<30; i++) legacy_bootkick(false,false);
assert(!bootkick_reset_triggered); // no first-boot reset
assert(!bootkick_reset_triggered);
legacy_bootkick(false,true); assert(observed_boot==BOOT_STANDBY);
legacy_bootkick(true,false); assert(observed_boot==BOOT_BOOTKICK);
for (int i=0; i<18; i++) { legacy_bootkick(true,false); assert(observed_boot==BOOT_BOOTKICK); }
legacy_bootkick(true,false); assert(observed_boot==BOOT_RESET && bootkick_reset_triggered);
assert(gpio_level); // Cuatro RESET is deasserted bootkick, NOT a separate reset pin
assert(gpio_level);
for (int i=0; i<4; i++) { legacy_bootkick(true,false); assert(observed_boot==BOOT_RESET); }
legacy_bootkick(true,false); assert(observed_boot==BOOT_BOOTKICK && !gpio_level);
legacy_bootkick(false,true); legacy_bootkick(true,false);
for (int i=0; i<30; i++) legacy_bootkick(true,false);
assert(observed_boot==BOOT_BOOTKICK); // only one reset per MCU boot
assert(observed_boot==BOOT_BOOTKICK);
}
static void cancel_reset_case(int serial) {
legacy_bootkick(false,true); legacy_bootkick(true,false);
@@ -135,7 +130,6 @@ static void cancel_reset_case(int serial) {
assert(!bootkick_reset_triggered && observed_boot==BOOT_BOOTKICK);
}
static void existing_case(void) {
// Harness insertion still wakes with ignition false.
legacy_bootkick(false,true); harness.status=1;
legacy_bootkick(false,false); assert(observed_boot==BOOT_BOOTKICK);
legacy_bootkick(false,true); assert(observed_boot==BOOT_STANDBY);
@@ -156,17 +150,15 @@ static void other_cars_case(void) {
gm_remote_start_boots_comma=true;
p=packet(0,0xC9,8,0,0); p.data[6]=0x10; ignition_can_hook(&p); assert(ignition_can);
p.data[6]=0; ignition_can_hook(&p); assert(!ignition_can);
// Pre-AP checksum and counter remain active.
p=packet(0,0x101,3,0,0); p.data[0]=8; p.data[1]=14; p.data[2]=24; ignition_can_hook(&p);
assert(!ignition_can); p.data[1]=15; p.data[2]=25; ignition_can_hook(&p); assert(ignition_can);
p.data[0]=0; p.data[1]=0; p.data[2]=99; ignition_can_hook(&p); assert(ignition_can);
p.data[1]=1; p.data[2]=3; ignition_can_hook(&p); assert(!ignition_can);
// Rivian sequential modulo-15, then Mazda.
p=packet(0,0x152,8,0,0); p.data[1]=14; p.data[7]=0x10; ignition_can_hook(&p);
assert(!ignition_can); p.data[1]=0; ignition_can_hook(&p); assert(ignition_can);
p=packet(0,0x9E,8,0,0); ignition_can_hook(&p); assert(!ignition_can);
p.data[0]=0xC0; ignition_can_hook(&p); assert(ignition_can);
assert(!wake_on_can); // none of these set Tesla-only wake flag
assert(!wake_on_can);
#ifdef PANDA_HKG_REMOTE_START
ignition_can=false; legacy_bootkick(false,true);
p=packet(1,0x384,8,0,0); p.data[3]=1; ignition_can_hook(&p);
@@ -177,26 +169,23 @@ static void other_cars_case(void) {
#endif
}
static void drive_edge_case(void) {
// Prolonged awake, SOM shuts down while fresh ACCESSORY CAN continues.
pair(2); second(); heartbeat_counter=0; second(); assert(observed_boot==BOOT_STANDBY);
for (unsigned i=0; i<3700; i++) { tesla(2,i%16); second(); }
assert(wake_on_can && !ignition_can && observed_boot==BOOT_STANDBY);
tesla(3,4); second(); assert(ignition_can && wake_on_can);
assert(observed_boot==BOOT_BOOTKICK); // Stock DRIVE edge must survive held wake.
assert(observed_boot==BOOT_BOOTKICK);
puts("PASS: fresh ACCESSORY through SOM shutdown preserves later DRIVE bootkick");
tesla(0,5); second(); tesla(2,6); second();
assert(observed_boot==BOOT_BOOTKICK); // true low->high recovers
assert(observed_boot==BOOT_BOOTKICK);
heartbeat_counter=0; second(); assert(observed_boot==BOOT_STANDBY);
// Car sleep / absent or rejected traffic must age wake out and permit re-wake.
for (unsigned i=0; i<5; i++) {
tesla(2,6); // repeated counter cannot keep the wake fresh
tesla(2,6);
second();
}
assert(!wake_on_can && !ignition_can && observed_boot==BOOT_STANDBY);
tesla(2,7); second(); assert(wake_on_can && observed_boot==BOOT_BOOTKICK);
}
static void stock_drive_case(void) {
// This exact physical-input sequence passes both stock and candidate.
pair(2); second(); heartbeat_counter=0; second();
for (unsigned i=0; i<3700; i++) { tesla(2,i%16); second(); }
assert(!ignition_can && observed_boot==BOOT_STANDBY);
@@ -205,25 +194,24 @@ static void stock_drive_case(void) {
}
static void independent_edges_case(void) {
test_bootkick(false,true,false); assert(observed_boot==BOOT_STANDBY);
test_bootkick(false,true,true); assert(observed_boot==BOOT_BOOTKICK); // edge beats heartbeat
test_bootkick(false,true,true); assert(observed_boot==BOOT_BOOTKICK);
test_bootkick(false,true,true); assert(observed_boot==BOOT_STANDBY);
test_bootkick(false,false,true); assert(observed_boot==BOOT_STANDBY); // held wake is not level-triggered
test_bootkick(true,true,true); assert(observed_boot==BOOT_BOOTKICK); // independent ignition edge
test_bootkick(false,false,true); assert(observed_boot==BOOT_STANDBY);
test_bootkick(true,true,true); assert(observed_boot==BOOT_BOOTKICK);
test_bootkick(true,true,false); assert(observed_boot==BOOT_STANDBY);
test_bootkick(true,false,true); assert(observed_boot==BOOT_STANDBY); // no new wake kick while started
test_bootkick(false,false,true); assert(observed_boot==BOOT_STANDBY); // falling ignition is not wake edge
test_bootkick(true,false,true); assert(observed_boot==BOOT_STANDBY);
test_bootkick(false,false,true); assert(observed_boot==BOOT_STANDBY);
test_bootkick(false,false,false); assert(observed_boot==BOOT_STANDBY);
test_bootkick(false,false,true); assert(observed_boot==BOOT_BOOTKICK);
test_bootkick(false,true,true); assert(observed_boot==BOOT_STANDBY);
harness.status=1;
test_bootkick(false,true,true); assert(observed_boot==BOOT_BOOTKICK); // harness still beats heartbeat
test_bootkick(false,true,true); assert(observed_boot==BOOT_BOOTKICK);
}
static void wake_reset_case(void) {
test_bootkick(false,true,false);
test_bootkick(false,false,true);
for (int i=0; i<18; i++) { test_bootkick(false,false,true); assert(observed_boot==BOOT_BOOTKICK); }
test_bootkick(false,false,true); assert(observed_boot==BOOT_RESET && bootkick_reset_triggered);
// Ignition, wake, harness and heartbeat cannot interrupt the active reset.
for (int i=0; i<4; i++) {
harness.status=(i%2)+1;
test_bootkick(i%2,true,i%2); assert(observed_boot==BOOT_RESET);
@@ -231,7 +219,7 @@ static void wake_reset_case(void) {
test_bootkick(true,false,true); assert(observed_boot==BOOT_BOOTKICK);
test_bootkick(false,true,false); test_bootkick(false,false,true);
for (int i=0; i<30; i++) test_bootkick(false,false,true);
assert(observed_boot==BOOT_BOOTKICK); // no second reset
assert(observed_boot==BOOT_BOOTKICK);
}
static void boot_trace_case(void) {
uint32_t rng=0x87654321;