From 9d75d2c39cb51ed66f6a037cdaff50123f9a580f Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 19 Jun 2026 02:19:08 -0600 Subject: [PATCH] dashboard: add OBD-II coolant polling over CAN --- docs/dashboard-esp32s3.md | 8 + .../esp32-s3-dashboard/dashboard_config.h | 2 +- .../esp32-s3-dashboard/esp32-s3-dashboard.ino | 182 +++++++++++++++++- 3 files changed, 189 insertions(+), 3 deletions(-) diff --git a/docs/dashboard-esp32s3.md b/docs/dashboard-esp32s3.md index b8eb4fe..731a714 100644 --- a/docs/dashboard-esp32s3.md +++ b/docs/dashboard-esp32s3.md @@ -311,3 +311,11 @@ The diagnostics overlay shows dashboard version, uptime, heap, WiFi RSSI/IP, Car - Swipe right from the main dashboard to open Battery Detail. - Swipe left from Battery Detail to return to the main dashboard. - Diagnostics remains hidden behind the multi-tap system-card gesture. + + +## OBD-II Coolant Polling +- Dashboard ESP32-S3 Touch LCD 5B uses onboard CAN/TWAI via GPIO15 CANTX and GPIO16 CANRX. +- First implemented PID is Mode 01 PID 05 coolant temperature. +- Sends standard 11-bit functional request ID `0x7DF` and accepts ECU responses from `0x7E8` through `0x7EF`. +- Diagnostics page shows TWAI state, coolant value, TX/RX counts, and last OBD status. +- Overview coolant gauge shows `--°` until a valid OBD response is received. diff --git a/firmware/esp32-s3-dashboard/dashboard_config.h b/firmware/esp32-s3-dashboard/dashboard_config.h index fa57f57..492ee21 100644 --- a/firmware/esp32-s3-dashboard/dashboard_config.h +++ b/firmware/esp32-s3-dashboard/dashboard_config.h @@ -1,6 +1,6 @@ #pragma once -static const char *DASHBOARD_VERSION = "0.1.120-temp-alert-style-real-fix"; +static const char *DASHBOARD_VERSION = "0.1.121-obd-coolant-polling"; // Update these if your Cargo ESP AP credentials are different. //static const char *CARGO_WIFI_SSID = "OverlandController"; diff --git a/firmware/esp32-s3-dashboard/esp32-s3-dashboard.ino b/firmware/esp32-s3-dashboard/esp32-s3-dashboard.ino index c175814..584b310 100644 --- a/firmware/esp32-s3-dashboard/esp32-s3-dashboard.ino +++ b/firmware/esp32-s3-dashboard/esp32-s3-dashboard.ino @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "lvgl_v8_port.h" @@ -15,6 +16,24 @@ using namespace esp_panel::drivers; using namespace esp_panel::board; static DashboardStatus status_model; +static constexpr gpio_num_t OBD_CAN_TX_PIN = GPIO_NUM_15; +static constexpr gpio_num_t OBD_CAN_RX_PIN = GPIO_NUM_16; +static constexpr uint32_t OBD_REQUEST_ID = 0x7DF; +static constexpr uint32_t OBD_RESPONSE_ID_MIN = 0x7E8; +static constexpr uint32_t OBD_RESPONSE_ID_MAX = 0x7EF; +static constexpr uint8_t OBD_PID_COOLANT_TEMP = 0x05; + +static bool obd_can_ready = false; +static bool obd_can_started = false; +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_rx_count = 0; +static int obd_coolant_temp_f = -999; +static int last_rendered_obd_coolant_temp_f = -1000; +static String obd_last_error = "not initialized"; + static lv_obj_t *battery_card_obj = nullptr; static lv_obj_t *battery_soc_arc = nullptr; @@ -369,6 +388,149 @@ static String format_diag_uptime(unsigned long seconds) return out; } +static void update_vehicle_coolant_widget() +{ + if (vehicle_coolant_value_label == nullptr || vehicle_coolant_meter == nullptr || vehicle_coolant_needle == nullptr) { + return; + } + + if (obd_coolant_temp_f == last_rendered_obd_coolant_temp_f) { + return; + } + + last_rendered_obd_coolant_temp_f = obd_coolant_temp_f; + + lvgl_port_lock(-1); + + if (obd_coolant_temp_f > -100) { + int bounded_temp = clamp_int(obd_coolant_temp_f, 100, 260); + lv_meter_set_indicator_value(vehicle_coolant_meter, vehicle_coolant_needle, bounded_temp); + lv_label_set_text(vehicle_coolant_value_label, (String(obd_coolant_temp_f) + "°").c_str()); + } else { + lv_meter_set_indicator_value(vehicle_coolant_meter, vehicle_coolant_needle, 100); + lv_label_set_text(vehicle_coolant_value_label, "--°"); + } + + lvgl_port_unlock(); +} + +static bool init_obd_can() +{ + if (obd_can_ready && obd_can_started) { + return true; + } + + twai_general_config_t general_config = TWAI_GENERAL_CONFIG_DEFAULT(OBD_CAN_TX_PIN, OBD_CAN_RX_PIN, TWAI_MODE_NORMAL); + general_config.tx_queue_len = 5; + general_config.rx_queue_len = 20; + + twai_timing_config_t timing_config = TWAI_TIMING_CONFIG_500KBITS(); + twai_filter_config_t filter_config = TWAI_FILTER_CONFIG_ACCEPT_ALL(); + + if (!obd_can_ready) { + esp_err_t install_result = twai_driver_install(&general_config, &timing_config, &filter_config); + if (install_result != ESP_OK && install_result != ESP_ERR_INVALID_STATE) { + obd_last_error = "driver install failed: " + String((int)install_result); + Serial.println("OBD CAN: " + obd_last_error); + return false; + } + + obd_can_ready = true; + } + + esp_err_t start_result = twai_start(); + if (start_result != ESP_OK && start_result != ESP_ERR_INVALID_STATE) { + obd_last_error = "start failed: " + String((int)start_result); + Serial.println("OBD CAN: " + obd_last_error); + return false; + } + + obd_can_started = true; + obd_last_error = "started"; + Serial.println("OBD CAN: TWAI started on TX GPIO15 / RX GPIO16 at 500 kbps"); + return true; +} + +static void send_obd_coolant_request() +{ + twai_message_t message = {}; + message.identifier = OBD_REQUEST_ID; + message.extd = 0; + message.rtr = 0; + message.data_length_code = 8; + message.data[0] = 0x02; + message.data[1] = 0x01; + message.data[2] = OBD_PID_COOLANT_TEMP; + message.data[3] = 0x55; + message.data[4] = 0x55; + message.data[5] = 0x55; + message.data[6] = 0x55; + message.data[7] = 0x55; + + esp_err_t result = twai_transmit(&message, 0); + if (result == ESP_OK) { + obd_tx_count++; + last_obd_tx_ms = millis(); + obd_last_error = "coolant request sent"; + } else { + obd_last_error = "tx failed: " + String((int)result); + } +} + +static void handle_obd_can_message(const twai_message_t &message) +{ + if (message.extd || message.rtr) { + return; + } + + if (message.identifier < OBD_RESPONSE_ID_MIN || message.identifier > OBD_RESPONSE_ID_MAX) { + return; + } + + if (message.data_length_code < 4) { + return; + } + + // Single-frame OBD-II response: + // byte0 length, byte1 0x41 response mode, byte2 PID, byte3 value A. + if (message.data[1] == 0x41 && message.data[2] == OBD_PID_COOLANT_TEMP) { + int coolant_c = (int)message.data[3] - 40; + obd_coolant_temp_f = (int)roundf((coolant_c * 9.0f / 5.0f) + 32.0f); + obd_rx_count++; + last_obd_rx_ms = millis(); + obd_last_error = "coolant response OK"; + + Serial.print("OBD CAN: coolant="); + Serial.print(obd_coolant_temp_f); + Serial.println(" F"); + + update_vehicle_coolant_widget(); + } +} + +static void process_obd_can() +{ + if (!init_obd_can()) { + return; + } + + if (millis() - last_obd_poll_ms >= 1000) { + last_obd_poll_ms = millis(); + send_obd_coolant_request(); + } + + twai_message_t message = {}; + while (twai_receive(&message, 0) == ESP_OK) { + handle_obd_can_message(message); + } + + if (millis() - last_obd_rx_ms > 5000 && obd_coolant_temp_f > -100) { + obd_coolant_temp_f = -999; + update_vehicle_coolant_widget(); + } +} + + static void update_diagnostics_overlay() { if (diagnostics_overlay == nullptr || diagnostics_label == nullptr) { @@ -442,6 +604,21 @@ static void update_diagnostics_overlay() text += String(status_model.cell_delta_mv); text += " mV"; + text += "\n\nOBD-II / CAN\n"; + text += " TWAI: "; + text += obd_can_started ? "started" : "offline"; + 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 += String(obd_tx_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 status: "; + text += obd_last_error; + text += "\n\nControls\n"; text += " Relays visible: "; text += String(relay_count); @@ -1885,7 +2062,7 @@ lv_obj_t *system_card = create_card(screen, "", 960, 48, LV_ALIGN_TOP_MID, 0, 12 lv_meter_set_indicator_end_value(vehicle_coolant_meter, hot_arc, 260); vehicle_coolant_needle = lv_meter_add_needle_line(vehicle_coolant_meter, coolant_scale, 4, lv_color_hex(0xFFFFFF), -14); - lv_meter_set_indicator_value(vehicle_coolant_meter, vehicle_coolant_needle, 188); + lv_meter_set_indicator_value(vehicle_coolant_meter, vehicle_coolant_needle, 100); lv_obj_t *vehicle_coolant_center_cap = lv_obj_create(vehicle_coolant_meter); lv_obj_set_size(vehicle_coolant_center_cap, 54, 54); @@ -1897,7 +2074,7 @@ lv_obj_t *system_card = create_card(screen, "", 960, 48, LV_ALIGN_TOP_MID, 0, 12 #endif vehicle_coolant_value_label = lv_label_create(vehicle_card); - lv_label_set_text(vehicle_coolant_value_label, "188°"); + lv_label_set_text(vehicle_coolant_value_label, "--°"); lv_obj_set_style_text_color(vehicle_coolant_value_label, lv_color_hex(0xFFFFFF), 0); lv_obj_set_style_text_font(vehicle_coolant_value_label, &lv_font_montserrat_40, 0); lv_obj_set_width(vehicle_coolant_value_label, 120); @@ -2037,6 +2214,7 @@ void loop() update_system_status_label(); process_pending_relay_command(); poll_status_api(); + process_obd_can(); process_dashboard_page_switch(); delay(50);