dashboard: add OBD-II coolant polling over CAN
This commit is contained in:
@@ -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 right from the main dashboard to open Battery Detail.
|
||||||
- Swipe left from Battery Detail to return to the main dashboard.
|
- Swipe left from Battery Detail to return to the main dashboard.
|
||||||
- Diagnostics remains hidden behind the multi-tap system-card gesture.
|
- 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
|
#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.
|
// Update these if your Cargo ESP AP credentials are different.
|
||||||
//static const char *CARGO_WIFI_SSID = "OverlandController";
|
//static const char *CARGO_WIFI_SSID = "OverlandController";
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <HTTPClient.h>
|
#include <HTTPClient.h>
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <esp_display_panel.hpp>
|
#include <esp_display_panel.hpp>
|
||||||
|
#include <driver/twai.h>
|
||||||
|
|
||||||
#include <lvgl.h>
|
#include <lvgl.h>
|
||||||
#include "lvgl_v8_port.h"
|
#include "lvgl_v8_port.h"
|
||||||
@@ -15,6 +16,24 @@ using namespace esp_panel::drivers;
|
|||||||
using namespace esp_panel::board;
|
using namespace esp_panel::board;
|
||||||
|
|
||||||
static DashboardStatus status_model;
|
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_card_obj = nullptr;
|
||||||
static lv_obj_t *battery_soc_arc = nullptr;
|
static lv_obj_t *battery_soc_arc = nullptr;
|
||||||
@@ -369,6 +388,149 @@ static String format_diag_uptime(unsigned long seconds)
|
|||||||
return out;
|
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()
|
static void update_diagnostics_overlay()
|
||||||
{
|
{
|
||||||
if (diagnostics_overlay == nullptr || diagnostics_label == nullptr) {
|
if (diagnostics_overlay == nullptr || diagnostics_label == nullptr) {
|
||||||
@@ -442,6 +604,21 @@ static void update_diagnostics_overlay()
|
|||||||
text += String(status_model.cell_delta_mv);
|
text += String(status_model.cell_delta_mv);
|
||||||
text += " 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 += "\n\nControls\n";
|
||||||
text += " Relays visible: ";
|
text += " Relays visible: ";
|
||||||
text += String(relay_count);
|
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);
|
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);
|
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_t *vehicle_coolant_center_cap = lv_obj_create(vehicle_coolant_meter);
|
||||||
lv_obj_set_size(vehicle_coolant_center_cap, 54, 54);
|
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
|
#endif
|
||||||
|
|
||||||
vehicle_coolant_value_label = lv_label_create(vehicle_card);
|
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_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_style_text_font(vehicle_coolant_value_label, &lv_font_montserrat_40, 0);
|
||||||
lv_obj_set_width(vehicle_coolant_value_label, 120);
|
lv_obj_set_width(vehicle_coolant_value_label, 120);
|
||||||
@@ -2037,6 +2214,7 @@ void loop()
|
|||||||
update_system_status_label();
|
update_system_status_label();
|
||||||
process_pending_relay_command();
|
process_pending_relay_command();
|
||||||
poll_status_api();
|
poll_status_api();
|
||||||
|
process_obd_can();
|
||||||
process_dashboard_page_switch();
|
process_dashboard_page_switch();
|
||||||
|
|
||||||
delay(50);
|
delay(50);
|
||||||
|
|||||||
Reference in New Issue
Block a user