From ed9611310ee91302bf6049e8cfa958c96577220f Mon Sep 17 00:00:00 2001 From: nick Date: Sun, 26 Jul 2026 14:49:19 -0600 Subject: [PATCH] dashboard: expand CAN diagnostics --- firmware/esp32-s3-dashboard/README.md | 11 +++ .../esp32-s3-dashboard/esp32-s3-dashboard.ino | 99 ++++++++++++++++++- 2 files changed, 107 insertions(+), 3 deletions(-) diff --git a/firmware/esp32-s3-dashboard/README.md b/firmware/esp32-s3-dashboard/README.md index 5af2e33..6c9678b 100644 --- a/firmware/esp32-s3-dashboard/README.md +++ b/firmware/esp32-s3-dashboard/README.md @@ -75,3 +75,14 @@ The dashboard uses field-filtered `/api/v1/status` calls: - Slow poll: `/api/v1/status?fields=network,system,config` This keeps the endpoint model simple while reducing payload size, JSON parsing, and UI churn. + +## OBD-II / CAN Diagnostics + +The hidden diagnostics overlay distinguishes raw CAN traffic from parsed +OBD-II coolant responses. It reports request successes and failures, total +raw frames received, the last frame identifier and DLC, TWAI queue depth, +controller error counters, missed or overrun frames, arbitration losses, +and bus errors. + +Raw RX activity confirms that the display is receiving CAN frames even when +the vehicle does not return a matching Mode 01 PID 05 coolant response. diff --git a/firmware/esp32-s3-dashboard/esp32-s3-dashboard.ino b/firmware/esp32-s3-dashboard/esp32-s3-dashboard.ino index 90f8e40..76f17dc 100644 --- a/firmware/esp32-s3-dashboard/esp32-s3-dashboard.ino +++ b/firmware/esp32-s3-dashboard/esp32-s3-dashboard.ino @@ -47,7 +47,13 @@ static unsigned long last_obd_poll_ms = 0; static unsigned long last_obd_rx_ms = 0; static unsigned long last_obd_tx_ms = 0; static uint32_t obd_tx_count = 0; +static uint32_t obd_tx_fail_count = 0; static uint32_t obd_rx_count = 0; +static uint32_t obd_raw_rx_count = 0; +static uint32_t obd_last_rx_identifier = 0; +static uint8_t obd_last_rx_dlc = 0; +static bool obd_last_rx_extended = false; +static unsigned long last_obd_raw_rx_ms = 0; static int obd_coolant_temp_f = -999; static int last_rendered_obd_coolant_temp_f = -1000; static String obd_last_error = "not initialized"; @@ -491,6 +497,7 @@ static void send_obd_coolant_request() last_obd_tx_ms = millis(); obd_last_error = "coolant request sent"; } else { + obd_tx_fail_count++; obd_last_error = "tx failed: " + String((int)result); } } @@ -539,6 +546,12 @@ static void process_obd_can() twai_message_t message = {}; while (twai_receive(&message, 0) == ESP_OK) { + obd_raw_rx_count++; + obd_last_rx_identifier = message.identifier; + obd_last_rx_dlc = message.data_length_code; + obd_last_rx_extended = message.extd; + last_obd_raw_rx_ms = millis(); + handle_obd_can_message(message); } @@ -625,15 +638,95 @@ static void update_diagnostics_overlay() text += "\n\nOBD-II / CAN\n"; text += " TWAI: "; text += obd_can_started ? "started" : "offline"; + text += " @ 500 kbps"; text += "\n Pins: TX GPIO15 / RX GPIO16"; text += "\n Coolant: "; text += obd_coolant_temp_f > -100 ? String(obd_coolant_temp_f) + "°F" : String("--"); - text += "\n TX/RX: "; + + text += "\n Requests OK/fail: "; text += String(obd_tx_count); text += "/"; + text += String(obd_tx_fail_count); + + text += "\n Raw RX/coolant RX: "; + text += String(obd_raw_rx_count); + text += "/"; text += String(obd_rx_count); - text += "\n Last RX age: "; - text += last_obd_rx_ms > 0 ? String((millis() - last_obd_rx_ms) / 1000) + "s" : String("--"); + + text += "\n Last frame: "; + if (obd_raw_rx_count > 0) { + text += obd_last_rx_extended ? "EXT 0x" : "STD 0x"; + text += String(obd_last_rx_identifier, HEX); + text += " DLC "; + text += String(obd_last_rx_dlc); + } else { + text += "--"; + } + + text += "\n Raw RX age: "; + text += last_obd_raw_rx_ms > 0 + ? String((millis() - last_obd_raw_rx_ms) / 1000) + "s" + : String("--"); + + text += "\n Coolant RX age: "; + text += last_obd_rx_ms > 0 + ? String((millis() - last_obd_rx_ms) / 1000) + "s" + : String("--"); + + if (obd_can_started) { + twai_status_info_t can_status = {}; + esp_err_t status_result = twai_get_status_info(&can_status); + + if (status_result == ESP_OK) { + const char *state_text = "unknown"; + + switch (can_status.state) { + case TWAI_STATE_STOPPED: + state_text = "stopped"; + break; + case TWAI_STATE_RUNNING: + state_text = "running"; + break; + case TWAI_STATE_BUS_OFF: + state_text = "bus-off"; + break; + case TWAI_STATE_RECOVERING: + state_text = "recovering"; + break; + default: + break; + } + + text += "\n State: "; + text += state_text; + + text += " TXq/RXq "; + text += String(can_status.msgs_to_tx); + text += "/"; + text += String(can_status.msgs_to_rx); + + text += "\n TEC/REC: "; + text += String(can_status.tx_error_counter); + text += "/"; + text += String(can_status.rx_error_counter); + + text += "\n Fail/miss/overrun: "; + text += String(can_status.tx_failed_count); + text += "/"; + text += String(can_status.rx_missed_count); + text += "/"; + text += String(can_status.rx_overrun_count); + + text += "\n Arb lost/bus errors: "; + text += String(can_status.arb_lost_count); + text += "/"; + text += String(can_status.bus_error_count); + } else { + text += "\n Driver status error: "; + text += String((int)status_result); + } + } + text += "\n Last status: "; text += obd_last_error;