Skip to content

Commit 6f9ceae

Browse files
committed
fix(detect): ignore all-zero 0x398 stub so HW4 trims aren't mislabelled Legacy
On HW4 Juniper/Giga the gateway forwards an empty GTW_carConfig (0x398) onto Bus 6 — only the mux byte moves. Reading byte0 das_hw=0 from that stub classified the car as Legacy and routed it through the 0x3EE path before 0x3FD upgraded it to HW3. Treat an all-zero 0x398 payload as Unknown in both the shared core and the ESP32 so detection falls through to live markers (0x39B on Vehicle CAN). Host test added (236 assertions). Also document in HARDWARE.md that 0x370 EPAS3P is absent from Vehicle CAN on HW4-modern (jewelrylin + DrStrangeglovebox dual-CAN captures), mirroring the 0x3C2-not-on-Bus-6 case. Refs #100 #110
1 parent 907cc7e commit 6f9ceae

4 files changed

Lines changed: 57 additions & 2 deletions

File tree

HARDWARE.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,19 @@ And these "Vehicle CAN" signals are also writable on bus 6:
225225
> one device: Bus 6 for `0x3FD` / `0x370` / `0x3F8` / TLSSC, and Vehicle CAN
226226
> direct for `0x3C2`. Slated as a v2.16 platformio variant.
227227
228+
> [!IMPORTANT]
229+
> **On HW4-modern, `0x370` EPAS3P_sysStatus is the mirror case of `0x3C2`: it is
230+
> absent from Vehicle CAN (X179 pin 9/10/11).** Dual-CAN captures on two cars —
231+
> @jewelrylin's Juniper RWD (0 / 20,760 frames over 60 s on pin 9/10) and
232+
> @DrStrangeglovebox's MYP Giga Berlin (0 / 2,653 on pin 10/11) — confirm `0x370`
233+
> only appears on **Bus 6 (pin 13/14)** as the gateway-forwarded copy. The EPAS
234+
> module does **not** receive `0x370` on Vehicle CAN on these trims, so relocating
235+
> the nag echo from Bus 6 to Vehicle CAN does **not** reach EPAS — it is not a
236+
> viable nag-killer pivot for HW4-modern. The only remaining X179 location that
237+
> could carry the EPAS-side frame is **Chassis Bus 3 (pin 18/19)**; until a
238+
> Listen-Only capture there confirms it, the 14.x HW4 nag path stays open. See
239+
> [#100](https://github.com/hypery11/flipper-tesla-fsd/issues/100).
240+
228241
**One bus, one connection, reads and writes almost everything.**
229242

230243
This is how the 非凡指揮官 (Feifan Commander, 69K+ sales in China)

esp32/.firmware/fsd_handler.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,20 @@ bool fsd_ap_first_allows(const FSDState *state, uint32_t now_ms) {
100100

101101
TeslaHWVersion fsd_detect_hw_version(const CanFrame *frame) {
102102
if (frame->id != CAN_ID_GTW_CAR_CONFIG) return TeslaHW_Unknown;
103+
// Some HW4 trims (Juniper/Giga) forward an all-zero 0x398 stub on the
104+
// gateway copy that reaches Bus 6 — only the mux byte ever moves. An empty
105+
// GTW_carConfig carries no real das_hw; reading it as 0 would mislabel the
106+
// car as Legacy and route it through the 0x3EE path. The real HW4 marker
107+
// lives on Vehicle CAN (0x39B). Treat an all-zero payload as Unknown so
108+
// detection falls through to the live markers instead of a stub.
109+
bool all_zero = true;
110+
for (uint8_t i = 0; i < frame->dlc && i < CAN_FRAME_MAX_DATA_LEN; i++) {
111+
if (frame->data[i] != 0u) {
112+
all_zero = false;
113+
break;
114+
}
115+
}
116+
if (all_zero) return TeslaHW_Unknown;
103117
// DAS_HWversion field: bits 7:6 of byte 0 (das_hw)
104118
uint8_t das_hw = (frame->data[SIG_GTW_DAS_HW_BYTE] >> SIG_GTW_DAS_HW_SHIFT) &
105119
SIG_GTW_DAS_HW_MASK;

fsd_logic/fsd_handler.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,20 @@ bool fsd_is_selected_in_ui(const CANFRAME* frame, bool force_fsd) {
9292

9393
TeslaHWVersion fsd_detect_hw_version(const CANFRAME* frame) {
9494
if(frame->canId != CAN_ID_GTW_CAR_CONFIG) return TeslaHW_Unknown;
95+
// Some HW4 trims (Juniper/Giga) forward an all-zero 0x398 stub on the
96+
// gateway copy that reaches Bus 6 — only the mux byte ever moves. An empty
97+
// GTW_carConfig carries no real das_hw; reading it as 0 would mislabel the
98+
// car as Legacy and route it through the 0x3EE path. The real HW4 marker
99+
// lives on Vehicle CAN (0x39B). Treat an all-zero payload as Unknown so
100+
// detection falls through to the live markers instead of a stub.
101+
bool all_zero = true;
102+
for(uint8_t i = 0; i < frame->data_lenght && i < 8u; i++) {
103+
if(frame->buffer[i] != 0u) {
104+
all_zero = false;
105+
break;
106+
}
107+
}
108+
if(all_zero) return TeslaHW_Unknown;
95109
uint8_t das_hw = (frame->buffer[0] >> 6) & 0x03;
96110
switch(das_hw) {
97111
case 0:

test/test_fsd_core.c

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,24 @@ static void test_detect_hw(void) {
9898
CHECK(fsd_detect_hw_version(&f) == TeslaHW_HW3, "das_hw=2 -> HW3");
9999
f.buffer[0] = 0xC0; // 0b11 = 3
100100
CHECK(fsd_detect_hw_version(&f) == TeslaHW_HW4, "das_hw=3 -> HW4");
101+
// Real Legacy car: das_hw=0/1 but GTW_carConfig is populated (VIN/options),
102+
// so a non-zero config byte distinguishes it from the all-zero stub below.
103+
f.buffer[3] = 0x55; // some real config payload
101104
f.buffer[0] = 0x00; // 0
102-
CHECK(fsd_detect_hw_version(&f) == TeslaHW_Legacy, "das_hw=0 -> Legacy");
105+
CHECK(fsd_detect_hw_version(&f) == TeslaHW_Legacy, "das_hw=0 + payload -> Legacy");
103106
f.buffer[0] = 0x40; // 1
104-
CHECK(fsd_detect_hw_version(&f) == TeslaHW_Legacy, "das_hw=1 -> Legacy");
107+
CHECK(fsd_detect_hw_version(&f) == TeslaHW_Legacy, "das_hw=1 + payload -> Legacy");
108+
// All-zero 0x398 stub (HW4 Juniper/Giga gateway copy on Bus 6): must NOT be
109+
// read as das_hw=0 -> Legacy; fall through to live markers instead.
110+
zero(&f);
111+
f.canId = CAN_ID_GTW_CAR_CONFIG;
112+
f.data_lenght = 8;
113+
CHECK(fsd_detect_hw_version(&f) == TeslaHW_Unknown, "all-zero 0x398 stub -> Unknown");
114+
// Guard boundary: a populated frame with das_hw=0 (byte0 low bits set, e.g.
115+
// a real config/mux value) is NOT all-zero, so it still classifies Legacy —
116+
// the guard must only fire on a fully empty payload, never over-fire.
117+
f.buffer[0] = 0x05; // byte0 nonzero, das_hw bits (7:6) = 0
118+
CHECK(fsd_detect_hw_version(&f) == TeslaHW_Legacy, "das_hw=0 populated -> Legacy (guard no over-fire)");
105119
f.canId = 0x123; // wrong id
106120
CHECK(fsd_detect_hw_version(&f) == TeslaHW_Unknown, "wrong id -> Unknown");
107121
}

0 commit comments

Comments
 (0)