#include <BluetoothSerial.h>
#include <LiquidCrystal_I2C.h>
// Initialize Bluetooth Serial and LCD
BluetoothSerial SerialBT;
LiquidCrystal_I2C lcd(0x27, 16, 2); // I2C address 0x27, 16 chars, 2 lines
// ELM327 Communication
String elm327_mac = ""; // Leave empty to connect to any ELM327
bool elm327_connected = false;
bool obd_initialized = false;
// OBD-II PID definitions
#define PID_RPM 0x0C
#define PID_ENGINE_TEMP 0x05
#define PID_VEHICLE_SPEED 0x0D
#define PID_THROTTLE_POS 0x11
// Display variables
unsigned long lastUpdate = 0;
const unsigned long updateInterval = 2000; // Update every 2 seconds (slower for older cars)
const int LED_PIN = 2;
const int PID_DELAY = 300;
// Data storage
int rpm = 0;
int engineTemp = 0;
int vehicleSpeed = 0;
int throttlePos = 0;
// Function declarations
void connectToELM327();
bool initializeOBD();
bool sendCommand(String command, String expectedResponse);
String sendCommandWithResponse(String command);
void updateOBDData();
void updateDisplay();
int hexToDec(String hexStr);
String cleanResponse(String response);
void setup() {
Serial.begin(115200);
// Initialize LED
pinMode(LED_PIN, OUTPUT);
// Initialize LCD
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("OBD-II Monitor");
lcd.setCursor(0, 1);
lcd.print("Starting...");
Serial.println("ESP32 OBD-II Monitor Starting...");
// Initialize Bluetooth in master mode
if (!SerialBT.begin("ESP32_OBD_Client", true)) {
Serial.println("Bluetooth initialization failed!");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("BT Init Failed");
while(1);
}
Serial.println("Bluetooth initialized. Searching for ELM327...");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Searching ELM327");
// Connect to ELM327
connectToELM327();
}
void loop() {
// Check if we need to reconnect
if (!elm327_connected) {
digitalWrite(LED_PIN, LOW);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Bluetooth connection lost.");
lcd.setCursor(1, 0);
lcd.print("Reconnecting...");
connectToELM327();
delay(1000); // Wait 1 second before retrying
return;
}
// Initialize OBD if not done
if (!obd_initialized) {
if (initializeOBD()) {
obd_initialized = true;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("OBD Connected");
delay(1000);
} else {
delay(1000); // Wait longer before retrying
return;
}
}
// Update data at regular intervals
if (millis() - lastUpdate >= updateInterval) {
updateOBDData();
lastUpdate = millis();
}
// Update LCD display
updateDisplay();
delay(200); // Longer delay for older OBD systems
}
void connectToELM327() {
Serial.println("Attempting to connect to ELM327...");
// Try to connect to ELM327 (usually named "OBDII" or similar)
if (SerialBT.connect("OBDII") || SerialBT.connect("ELM327") || SerialBT.connect("V1.5")) {
elm327_connected = true;
digitalWrite(LED_PIN, HIGH);
Serial.println("Connected to ELM327!");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("ELM327 Connected");
delay(2000);
} else {
elm327_connected = false;
digitalWrite(LED_PIN, LOW);
Serial.println("Failed to connect to ELM327");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("ELM327 Not Found");
lcd.setCursor(0, 1);
lcd.print("Retrying...");
}
}
bool initializeOBD() {
Serial.println("Initializing OBD-II communication...");
// Clear buffer first
while (SerialBT.available()) {
SerialBT.read();
}
delay(500);
// Reset ELM327
SerialBT.println("ATZ");
delay(3000); // Longer delay for reset
String response = "";
while (SerialBT.available()) {
response += (char)SerialBT.read();
}
Serial.println("Reset response: " + response);
// Turn off echo
if (!sendCommand("ATE0", "OK")) {
Serial.println("Echo off failed");
return false;
}
delay(PID_DELAY);
// Turn off line feeds
if (!sendCommand("ATL0", "OK")) {
Serial.println("Line feeds off failed");
// Continue anyway, not critical
}
delay(PID_DELAY);
// Turn off spaces
if (!sendCommand("ATS0", "OK")) {
Serial.println("Spaces off failed");
// Continue anyway, not critical
}
delay(PID_DELAY);
// Set timeout to longer value for older cars
if (!sendCommand("ATST96", "OK")) {
Serial.println("Timeout setting failed");
// Continue anyway
}
delay(PID_DELAY);
// Try specific protocols for 1997 Jeep
// First try ISO 9141-2 (protocol 3)
SerialBT.println("ATSP3");
delay(1000);
response = "";
while (SerialBT.available()) {
response += (char)SerialBT.read();
}
Serial.println("Protocol 3 response: " + response);
// Try a simple PID request to test connection
SerialBT.println("010C"); // RPM request
delay(2000); // Wait longer for older cars
response = "";
unsigned long startTime = millis();
while (millis() - startTime < 5000 && SerialBT.available()) {
response += (char)SerialBT.read();
}
Serial.println("RPM test response: " + response);
if (response.indexOf("410C") != -1 || response.indexOf("41 0C") != -1) {
Serial.println("OBD-II initialization successful with ISO 9141-2!");
return true;
}
// If that fails, try KWP2000 (protocol 2)
SerialBT.println("ATSP2");
delay(1000);
response = "";
while (SerialBT.available()) {
response += (char)SerialBT.read();
}
Serial.println("Protocol 2 response: " + response);
// Try RPM again
SerialBT.println("010C");
delay(2000);
response = "";
startTime = millis();
while (millis() - startTime < 5000 && SerialBT.available()) {
response += (char)SerialBT.read();
}
Serial.println("RPM test response (protocol 2): " + response);
if (response.indexOf("410C") != -1 || response.indexOf("41 0C") != -1) {
Serial.println("OBD-II initialization successful with KWP2000!");
return true;
}
Serial.println("OBD-II initialization failed - trying auto protocol");
// Last resort: try auto protocol
SerialBT.println("ATSP0");
delay(1000);
SerialBT.println("010C");
delay(3000);
response = "";
startTime = millis();
while (millis() - startTime < 5000 && SerialBT.available()) {
response += (char)SerialBT.read();
}
if (response.indexOf("410C") != -1 || response.indexOf("41 0C") != -1) {
Serial.println("OBD-II initialization successful with auto protocol!");
return true;
}
return false;
}
bool sendCommand(String command, String expectedResponse) {
// Clear any existing data
while (SerialBT.available()) {
SerialBT.read();
}
// Send command
SerialBT.println(command);
Serial.println("Sent: " + command);
// Wait for response
String response = "";
unsigned long startTime = millis();
while (millis() - startTime < 4000) { // 4 second timeout
if (SerialBT.available()) {
char c = SerialBT.read();
response += c;
if (response.indexOf(expectedResponse) != -1) {
Serial.println("Received: " + response);
return true;
}
}
}
Serial.println("Command timeout or unexpected response: " + response);
return false;
}
String sendCommandWithResponse(String command) {
// Clear any existing data
while (SerialBT.available()) {
SerialBT.read();
}
// Send command
SerialBT.println(command);
Serial.println("Sent: " + command);
// Wait for response
String response = "";
unsigned long startTime = millis();
while (millis() - startTime < 5000) { // 5 second timeout for data commands
if (SerialBT.available()) {
char c = SerialBT.read();
response += c;
}
// Check if we have a complete response
if (response.indexOf('>') != -1 || response.length() > 20) {
break;
}
}
// Clean up response
response = cleanResponse(response);
Serial.println("Received: " + response);
return response;
}
String cleanResponse(String response) {
response.replace("\r", "");
response.replace("\n", "");
response.replace(">", "");
response.replace(" ", "");
response.toUpperCase();
return response;
}
void updateOBDData() {
// Read RPM (PID 0x0C)
String response = sendCommandWithResponse("010C");
if (response.length() >= 6 && response.substring(0, 4) == "410C") {
String rpmHex = response.substring(4);
if (rpmHex.length() >= 4) {
rpm = (hexToDec(rpmHex.substring(0, 2)) * 256 + hexToDec(rpmHex.substring(2, 4))) / 4;
}
}
delay(300); // Give ELM327 time between commands
// Read Engine Temperature (PID 0x05)
response = sendCommandWithResponse("0105");
if (response.length() >= 6 && response.substring(0, 4) == "4105") {
String tempHex = response.substring(4);
if (tempHex.length() >= 2) {
int tempC = hexToDec(tempHex.substring(0, 2)) - 40; // Convert to Celsius first
engineTemp = (tempC * 9 / 5) + 32; // Convert to Fahrenheit
}
}
delay(300); // Give ELM327 time between commands
// Read Vehicle Speed (PID 0x0D) - may not be supported on 1997 Jeep
response = sendCommandWithResponse("010D");
if (response.length() >= 6 && response.substring(0, 4) == "410D") {
String speedHex = response.substring(4);
if (speedHex.length() >= 2) {
int speedKmh = hexToDec(speedHex.substring(0, 2));
vehicleSpeed = (speedKmh * 10) / 16; // Convert km/h to mph (multiply by 0.621371, simplified)
}
} else {
vehicleSpeed = -1; // Indicate not supported
}
delay(300); // Give ELM327 time between commands
// Read Throttle Position (PID 0x11) - may not be supported on 1997 Jeep
response = sendCommandWithResponse("0111");
if (response.length() >= 6 && response.substring(0, 4) == "4111") {
String throttleHex = response.substring(4);
if (throttleHex.length() >= 2) {
throttlePos = (hexToDec(throttleHex.substring(0, 2)) * 100) / 255;
}
} else {
throttlePos = -1; // Indicate not supported
}
Serial.printf("RPM: %d, Temp: %dF, Speed: %d mph, Throttle: %d%%\n",
rpm, engineTemp, vehicleSpeed, throttlePos);
}
void updateDisplay() {
lcd.clear();
// Top row: RPM and Temperature
lcd.setCursor(0, 0);
lcd.print("RPM:");
if (rpm > 0) {
lcd.print(rpm);
} else {
lcd.print("N/A");
}
lcd.setCursor(9, 0);
lcd.print("T:");
if (engineTemp > -40) {
lcd.print(engineTemp);
lcd.print("F");
} else {
lcd.print("N/A");
}
// Bottom row: Throttle Position and Vehicle Speed
lcd.setCursor(0, 1);
lcd.print("TP:");
if (throttlePos >= 0) {
lcd.print(throttlePos);
lcd.print("%");
} else {
lcd.print("N/A");
}
lcd.setCursor(9, 1);
lcd.print("VS:");
if (vehicleSpeed >= 0) {
lcd.print(vehicleSpeed);
} else {
lcd.print("N/A");
}
}
int hexToDec(String hexStr) {
if (hexStr.length() == 0) return 0;
int decimal = 0;
int base = 1;
int len = hexStr.length();
for (int i = len - 1; i >= 0; i--) {
char c = hexStr.charAt(i);
int digit;
if (c >= '0' && c <= '9') {
digit = c - '0';
} else if (c >= 'A' && c <= 'F') {
digit = c - 'A' + 10;
} else if (c >= 'a' && c <= 'f') {
digit = c - 'a' + 10;
} else {
return 0; // Invalid hex character
}
decimal += digit * base;
base *= 16;
}
return decimal;
}