dashboard: add OBD-II coolant polling over CAN

This commit is contained in:
2026-06-19 02:19:08 -06:00
parent 97e49a5d98
commit 9d75d2c39c
3 changed files with 189 additions and 3 deletions
+8
View File
@@ -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.
@@ -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";
@@ -3,6 +3,7 @@
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <esp_display_panel.hpp>
#include <driver/twai.h>
#include <lvgl.h>
#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);