diff --git a/esp32/.firmware/can_signals.h b/esp32/.firmware/can_signals.h
index c33957d..7eca337 100644
--- a/esp32/.firmware/can_signals.h
+++ b/esp32/.firmware/can_signals.h
@@ -36,6 +36,7 @@
#define SIG_AP_UI_FSD_SELECTED_MASK 0x01u
#define SIG_AP_FSD_ENABLE_BIT 46
+#define SIG_AP_CONTINUE_ON_GREEN_BIT 39
#define SIG_AP_NAG_CLEAR_BIT 19
#define SIG_AP_HW4_FSD_ENABLE_BIT 60
#define SIG_AP_HW4_EMERGENCY_VEHICLE_BIT 59
diff --git a/esp32/.firmware/fsd_handler.cpp b/esp32/.firmware/fsd_handler.cpp
index 9b27417..0a28f7b 100644
--- a/esp32/.firmware/fsd_handler.cpp
+++ b/esp32/.firmware/fsd_handler.cpp
@@ -47,6 +47,7 @@ void fsd_state_init(FSDState *state, TeslaHWVersion hw) {
state->ignore_ota = false;
state->emergency_vehicle_detect = false;
state->summon_unlock = false; // opt-in Summon EU Unlock, default OFF
+ state->continue_on_green = false; // opt-in Continue on Green, default OFF
state->fsd_unlock = false;
state->force_fsd = false;
state->china_mode = false;
@@ -203,6 +204,13 @@ bool fsd_handle_autopilot_frame(FSDState *state, CanFrame *frame) {
// mux 0 is the authoritative "is FSD requested" mux
if (mux == CAN_MUX_0) state->fsd_enabled = fsd_ui;
+ // bit39 continue-on-green with lead car (ev-open-can-tools TSLLC plugin);
+ // same 0x3FD mux0 frame on HW3/HW4, so apply before the HW split; pairs with TLSSC
+ if (mux == CAN_MUX_0 && state->continue_on_green && state->fsd_enabled) {
+ set_bit(frame, SIG_AP_CONTINUE_ON_GREEN_BIT, true); // bit39 continue-on-green
+ modified = true;
+ }
+
if (state->hw_version == TeslaHW_HW3) {
// ── HW3 ──────────────────────────────────────────────────────────────
if (mux == CAN_MUX_0 && state->fsd_unlock && state->fsd_enabled) {
diff --git a/esp32/.firmware/prefs.cpp b/esp32/.firmware/prefs.cpp
index aa1790f..cfdfccd 100644
--- a/esp32/.firmware/prefs.cpp
+++ b/esp32/.firmware/prefs.cpp
@@ -30,6 +30,7 @@ void prefs_load(FSDState *state) {
state->precondition = g_prefs.getBool("precond",false);
state->emergency_vehicle_detect = g_prefs.getBool("emrg", false);
state->summon_unlock = g_prefs.getBool("summon", false);
+ state->continue_on_green = g_prefs.getBool("cog", false);
state->bms_output = g_prefs.getBool("bms", false);
state->firmware_14x_warning = g_prefs.getBool("14x", true);
state->blackbox_enabled = g_prefs.getBool("bbx", BLACKBOX_DEFAULT_ENABLED);
@@ -63,9 +64,10 @@ void prefs_load(FSDState *state) {
state->cfg_steer_hi = g_prefs.getUChar("cshi", 1);
state->cfg_steer_lo = g_prefs.getUChar("cslo", 0);
- Serial.printf("[NVS] Loaded: FSDUnlock=%d NAG=%d ContinuousAP=%d IgnoreOTA=%d China=%d Chime=%d Summon=%d Sleep=%u AP=\"%s\" STA=\"%s\" HIDDEN=%d\n",
+ Serial.printf("[NVS] Loaded: FSDUnlock=%d NAG=%d ContinuousAP=%d IgnoreOTA=%d China=%d Chime=%d Summon=%d COG=%d Sleep=%u AP=\"%s\" STA=\"%s\" HIDDEN=%d\n",
state->fsd_unlock, state->nag_killer, state->continuous_ap, state->ignore_ota,
state->china_mode, state->suppress_speed_chime, state->summon_unlock,
+ state->continue_on_green,
state->sleep_idle_ms, state->wifi_ssid, state->wifi_sta_ssid,
state->wifi_hidden);
g_prefs.end();
@@ -99,6 +101,7 @@ void prefs_save(const FSDState *state) {
g_prefs.putBool("precond",state->precondition);
g_prefs.putBool("emrg", state->emergency_vehicle_detect);
g_prefs.putBool("summon", state->summon_unlock);
+ g_prefs.putBool("cog", state->continue_on_green);
g_prefs.putBool("bms", state->bms_output);
g_prefs.putBool("14x", state->firmware_14x_warning);
g_prefs.putBool("bbx", state->blackbox_enabled);
@@ -131,9 +134,10 @@ void prefs_save(const FSDState *state) {
g_prefs.putUChar("cshi", state->cfg_steer_hi);
g_prefs.putUChar("cslo", state->cfg_steer_lo);
- Serial.printf("[NVS] Saved: FSDUnlock=%d NAG=%d ContinuousAP=%d IgnoreOTA=%d China=%d Chime=%d Summon=%d Sleep=%u AP=\"%s\" STA=\"%s\" HIDDEN=%d\n",
+ Serial.printf("[NVS] Saved: FSDUnlock=%d NAG=%d ContinuousAP=%d IgnoreOTA=%d China=%d Chime=%d Summon=%d COG=%d Sleep=%u AP=\"%s\" STA=\"%s\" HIDDEN=%d\n",
state->fsd_unlock, state->nag_killer, state->continuous_ap, state->ignore_ota,
state->china_mode, state->suppress_speed_chime, state->summon_unlock,
+ state->continue_on_green,
state->sleep_idle_ms, state->wifi_ssid, state->wifi_sta_ssid,
state->wifi_hidden);
g_prefs.end();
diff --git a/esp32/.firmware/web_dashboard.cpp b/esp32/.firmware/web_dashboard.cpp
index 40178c2..73802ce 100644
--- a/esp32/.firmware/web_dashboard.cpp
+++ b/esp32/.firmware/web_dashboard.cpp
@@ -499,6 +499,10 @@ input:checked+.sl2:before{transform:translateX(20px);background:#fff}
Summon EU Unlock
+
+ Continue on Green
pairs with TLSSC
+
+
Looks like variant ? — the standard parser can't read AP-state on this bus.
@@ -961,6 +965,7 @@ function upd(d){
if(document.getElementById('rowChime')) document.getElementById('rowChime').style.display=d.isa_speed_enabled?'flex':'none';
if(document.getElementById('swTlssc')) document.getElementById('swTlssc').checked=d.tlssc_restore;
if(document.getElementById('swSummon')) document.getElementById('swSummon').checked=d.summon_unlock;
+ if(document.getElementById('swCog')) document.getElementById('swCog').checked=d.continue_on_green;
if(document.getElementById('swDisp')) document.getElementById('swDisp').checked=!!d.display_enabled;
if(document.activeElement.id!=='dispBr' && document.getElementById('dispBr'))
document.getElementById('dispBr').value=d.display_brightness||50;
@@ -1547,6 +1552,7 @@ static String build_json() {
j += "\"suppress_speed_chime\":"; j += state.suppress_speed_chime ? "true" : "false"; j += ',';
j += "\"tlssc_restore\":"; j += state.tlssc_restore ? "true" : "false"; j += ',';
j += "\"summon_unlock\":"; j += state.summon_unlock ? "true" : "false"; j += ',';
+ j += "\"continue_on_green\":"; j += state.continue_on_green ? "true" : "false"; j += ',';
j += "\"firmware_14x_warning\":"; j += state.firmware_14x_warning ? "true" : "false"; j += ',';
#if defined(BOARD_TTGO_DISPLAY)
j += "\"display_enabled\":"; j += state.display_enabled ? "true" : "false"; j += ',';
@@ -1991,6 +1997,18 @@ static void ws_event(uint8_t num, WStype_t type,
Serial.printf("[Web] Summon EU Unlock: %s\n", enabled ? "ON" : "OFF");
prefs_save(&saved);
}
+ } else if (strstr(buf, "\"continue_on_green\"")) {
+ if (vptr) {
+ while (*vptr == ' ' || *vptr == ':') vptr++;
+ bool enabled = (strncmp(vptr, "true", 4) == 0);
+ FSDState saved;
+ state_enter();
+ g_state->continue_on_green = enabled;
+ saved = *g_state;
+ state_exit();
+ Serial.printf("[Web] Continue on Green: %s\n", enabled ? "ON" : "OFF");
+ prefs_save(&saved);
+ }
} else if (strstr(buf, "\"dump\"")) {
if (vptr) {
while (*vptr == ' ' || *vptr == ':') vptr++;
diff --git a/fsd_logic/fsd_handler.c b/fsd_logic/fsd_handler.c
index e74475b..59b2f97 100644
--- a/fsd_logic/fsd_handler.c
+++ b/fsd_logic/fsd_handler.c
@@ -18,6 +18,7 @@ void fsd_state_init(FSDState* state, TeslaHWVersion hw) {
state->das_prev_hands_on_state = 0xFF; // escalation-edge baseline (#100)
state->enhanced_autopilot = false;
state->summon_unlock = false; // opt-in Summon EU Unlock, default OFF
+ state->continue_on_green = false; // opt-in Continue on Green, default OFF
state->speed_profile_locked = false;
state->hw4_offset = 0;
}
@@ -215,6 +216,12 @@ bool fsd_handle_autopilot_frame(FSDState* state, CANFRAME* frame, uint32_t now_m
modified = true;
}
+ // bit39 continue-on-green with lead car (ev-open-can-tools TSLLC plugin); pairs with TLSSC/bit38
+ if(mux == 0 && state->continue_on_green && state->fsd_enabled) {
+ fsd_set_bit(frame, 39, true);
+ modified = true;
+ }
+
if(state->hw_version == TeslaHW_HW3) {
if(mux == 0 && state->fsd_enabled) {
int raw = (int)((frame->buffer[3] >> 1) & 0x3F) - 30;
diff --git a/fsd_logic/fsd_state.h b/fsd_logic/fsd_state.h
index 2c1f9c0..039ed79 100644
--- a/fsd_logic/fsd_state.h
+++ b/fsd_logic/fsd_state.h
@@ -239,6 +239,7 @@ typedef struct FSDState {
// --- 0x3FD mux1 extras ---
bool assist_show_lane_graph; // bit45: lane visualization on non-FSD tier
bool assist_tlssc_bit38; // bit38 on mux0: explicit TLSSC enable (complementary to 0x331)
+ bool continue_on_green; // bit39 on mux0 (UI_fsdContinueOnGreenWithCIPV): continue through a green light behind a lead car without stalk confirm; opt-in, default OFF, pairs with TLSSC/bit38
// --- telemetry disable (0x3F8 bit43) ---
bool assist_telemetry_off; // force UI_enableTripTelemetry=0
diff --git a/test/test_fsd_core.c b/test/test_fsd_core.c
index f42d0b8..ea042dd 100644
--- a/test/test_fsd_core.c
+++ b/test/test_fsd_core.c
@@ -262,6 +262,61 @@ static void test_summon_unlock(void) {
CHECK((f.buffer[5] & 0x80) != 0, "HW4 mux1 summon-off bit47 still set (prior behavior)");
}
+// ── Continue on Green (0x3FD mux0 bit39) — opt-in, HW3 + HW4 ──────────────────
+// ev-open-can-tools TSLLC plugin sets UI_fsdContinueOnGreenWithCIPV (bit39) on
+// mux0. It is gated by continue_on_green + fsd_enabled and applies before the
+// HW3/HW4 split, so bit39 must appear for both hardware versions when ON, and
+// never when the toggle is OFF. bit39 -> byte 4 bit 7 -> mask 0x80.
+static void test_continue_on_green(void) {
+ CANFRAME f;
+
+ // ── HW3 ──
+ FSDState h3;
+ memset(&h3, 0, sizeof(h3));
+ h3.hw_version = TeslaHW_HW3;
+ h3.force_fsd = true; // makes mux0 select FSD -> fsd_enabled = true
+
+ // ON: bit39 set on mux0.
+ h3.continue_on_green = true;
+ zero(&f);
+ f.data_lenght = 8;
+ f.buffer[0] = 0; // mux0
+ CHECK(fsd_handle_autopilot_frame(&h3, &f, 0), "HW3 mux0 cog-on reports modified");
+ CHECK(h3.fsd_enabled, "HW3 mux0 sets fsd_enabled");
+ CHECK((f.buffer[4] & 0x80) != 0, "HW3 mux0 cog-on bit39 set");
+
+ // OFF: bit39 NOT set.
+ h3.continue_on_green = false;
+ zero(&f);
+ f.data_lenght = 8;
+ f.buffer[0] = 0;
+ fsd_handle_autopilot_frame(&h3, &f, 0);
+ CHECK((f.buffer[4] & 0x80) == 0, "HW3 mux0 cog-off bit39 NOT set");
+
+ // ── HW4 ──
+ FSDState h4;
+ memset(&h4, 0, sizeof(h4));
+ h4.hw_version = TeslaHW_HW4;
+ h4.force_fsd = true;
+
+ // ON: bit39 set on mux0.
+ h4.continue_on_green = true;
+ zero(&f);
+ f.data_lenght = 8;
+ f.buffer[0] = 0;
+ CHECK(fsd_handle_autopilot_frame(&h4, &f, 0), "HW4 mux0 cog-on reports modified");
+ CHECK(h4.fsd_enabled, "HW4 mux0 sets fsd_enabled");
+ CHECK((f.buffer[4] & 0x80) != 0, "HW4 mux0 cog-on bit39 set");
+
+ // OFF: bit39 NOT set.
+ h4.continue_on_green = false;
+ zero(&f);
+ f.data_lenght = 8;
+ f.buffer[0] = 0;
+ fsd_handle_autopilot_frame(&h4, &f, 0);
+ CHECK((f.buffer[4] & 0x80) == 0, "HW4 mux0 cog-off bit39 NOT set");
+}
+
// ── 0x399 ISA speed chime: bit + Tesla additive checksum ──────────────────────
static void test_isa_checksum(void) {
CANFRAME f;
@@ -1944,6 +1999,7 @@ int main(void) {
test_autopilot_hw4();
test_autopilot_hw3();
test_summon_unlock();
+ test_continue_on_green();
test_isa_checksum();
test_di_speed();
test_tlssc_restore();