Custom microprocessor (Arduino, Pi, etc) Jeep project thread

Yep.

There's a lot of stuff I remember doing, but don't remember how. I started with math and am comfortable there now, started statistics today and it's brand new because I never even took stats in college...it was optional and I think I took numerical methods instead.

After that I hope it smooths out as I get into the actual engineering application of stuff. That was always easier for me than the theoretical stuff. Like, I've never in my career had to look at an equation and decide whether it described an ellipse or a hyperbola.

Good luck

you-can-do-it-go-for-it.gif
 
Yep.

There's a lot of stuff I remember doing, but don't remember how. I started with math and am comfortable there now, started statistics today and it's brand new because I never even took stats in college...it was optional and I think I took numerical methods instead.

After that I hope it smooths out as I get into the actual engineering application of stuff. That was always easier for me than the theoretical stuff. Like, I've never in my career had to look at an equation and decide whether it described an ellipse or a hyperbola.
I took the FE and the PE in mechanical back in the dark ages (1990), one year out of college, but I took a review class to prepare for both because I was told that you need a strategy to pass the exam, and the course did that - I found that very helpful. By the way, back then, the PE was 10 questions, but they were multi-part - like a college exam. You had to show your calculations, and that's how you were graded. Supposedly, they used professors to grade the exams.

That process was definitely easier than when I took the PE in civil 18 years later (in 2007). Since I never had any civil courses in college, I took a Testmasters in-person "review" class, but instead of treating it as a review class, I used it to put together a study schedule and studied for six months - basically teaching myself enough civil engineering to pass the exam. I thought Testmasters was such a good program that in my last job, I paid for a Testmasters course for all EITs sitting for the PE exam that worked for me (half up front, the other half paid upon passing). Over the years, the passing rate was very high for those employees - probably 95%, much higher than the average passing rate.

I can't recommend Testmasters(.com) enough for preparing for the PE exam, but most civil engineering students take the FE in their senior year, so I have no experience with their FE prep course. However, I'd bet it's worth the money. Their PE courses are expensive, time-consuming, but worth the investment. Perhaps you could get your employer to pay for the courses?

Good luck! As App posted, "You can do it!"
 
I took the FE and the PE in mechanical back in the dark ages (1990), one year out of college, but I took a review class to prepare for both because I was told that you need a strategy to pass the exam, and the course did that - I found that very helpful. By the way, back then, the PE was 10 questions, but they were multi-part - like a college exam. You had to show your calculations, and that's how you were graded. Supposedly, they used professors to grade the exams.

That process was definitely easier than when I took the PE in civil 18 years later (in 2007). Since I never had any civil courses in college, I took a Testmasters in-person "review" class, but instead of treating it as a review class, I used it to put together a study schedule and studied for six months - basically teaching myself enough civil engineering to pass the exam. I thought Testmasters was such a good program that in my last job, I paid for a Testmasters course for all EITs sitting for the PE exam that worked for me (half up front, the other half paid upon passing). Over the years, the passing rate was very high for those employees - probably 95%, much higher than the average passing rate.

I can't recommend Testmasters(.com) enough for preparing for the PE exam, but most civil engineering students take the FE in their senior year, so I have no experience with their FE prep course. However, I'd bet it's worth the money. Their PE courses are expensive, time-consuming, but worth the investment. Perhaps you could get your employer to pay for the courses?

Good luck! As App posted, "You can do it!"

I'm using Kaplans PPI2pass for the FE, it seemed to have among the best reviews and had an on-demand (vs live video) option that I didn't consider overly expensive. I'll look into your recommendation once I pass the FE, thanks for that! I've already met the experience requirement so I'll roll right into PE registration and prep as soon as I have the FE result.

I may ask my employer to pay for the PE review but I won't for the FE because I should have done it in 2006.
 
  • Like
Reactions: Apparition and sab
I'm far behind the curve on this stuff, but I started catching up this week. I got an ESP32 (not the nano like you're using) and set off to make it talk to my bluetooth OBDII dongle (my primary unit is an OBDLink CX, which I really like) but apparently there are some BLE & security stuff that make it challenging to talk to the esp32. So then I got a chinesium ELM327 dongle, and I think it just sucked out loud because it wouldn't connect either. Then I replaced it with a slightly more expensive ($12) VEEPEAK unit and it connected right away.

IMG_4147.jpeg


Of course my office can't quite reach my garage to do bluetoothy things, and I didn't want to move my laptop (and give up my massive monitor setup), so I had it use a socketio to log the messages to a webpage viewable on my phone. That proved to be not a perfect solution, because the amount of code/test/code/test made not having the laptop in the garage impractical.

I mostly used chatGPT & Claude to generate the code...I barely speak C++ and I certainly don't know how to interface with a serial device. But after seeing it I mostly understand, and will be able to extrapolate. At any rate, it took a minute (read: much longer than a minute) to figure out that I needed to add some code to auto-negotiate the protocol, but as of last night I had my first success.

IMG_4146.jpeg



Longer term, I want to build a little sensor array that will allow me (or somebody else) to easily perform the 32RH pressure tests. The manual uses 2 pressure sensors that you have to repeatedly move around to conduct the test. You can get a transducer for like $10 on amazon, so I figure, why not just get enough to do the test in one pass? Then collect that data along with the RPM data needed for the test, graph/evaluate it against the test criteria, and Bob's your uncle.

Even longer term I'm thinking of streaming obdii data to something like Redpanda & Timeplus and then having a grafana dashboard on a display in my garage. Yeah, my phone app already does that, but this is a cool way to play with a bunch of tech. But that's down the road a bit.
 
Which VEEPEAK device did you get, the bright blue transparent case one?
https://www.amazon.com/Veepeak-Blue...DashCommand/dp/B011NSX27A/?tag=wranglerorg-20

I thought about getting a OBDLink CX because I like my OBDLink SX (USB cable). But from what I could find, they encrypt the Bluetooth stream, and use a non-published PIN. I'm not sure if a RasPi Pico W or an ESP32 or ESP8266 can deal with that. So I'm looking for a cheap way to experiment without wasting money on the many chinesium devices which just don't work well.
 
I'm using this exact one, which does have a blue transparent case: https://www.amazon.com/dp/B011NSX27A?tag=wranglerorg-20

How the OBDLinkCX connects is confusing to me, because (a) I don't believe I've ever used a pin to connect it, and I swear I've used it with multiple apps, so they can't all know the secret pin or have the certificate or whatever tricky thing it's using to connect. And it's possible I could've overcome it, but I didn't want the first step in this journey to be how to overcome bluetooth security so I spent $12 in the name of progress. :).

That said, for just ordinary monitoring, code reading, etc, I am extremely happy with the OBDLink CX and the OBDLink app (and now I'm wondering if I have in fact used the dongle with other apps). But this thread is all about doing things the hard way. :p
 
  • Like
Reactions: OldBuzzard
...I mostly used chatGPT & Claude to generate the code...I barely speak C++ and I certainly don't know how to interface with a serial device. But after seeing it I mostly understand, and will be able to extrapolate. At any rate, it took a minute (read: much longer than a minute) to figure out that I needed to add some code to auto-negotiate the protocol, but as of last night I had my first success...

Would you mind sharing your code?

I have an ESP32 which will scan for Bluetooth devices, and finds the VEEPEAK dongle (gets its name and MAC address), and pairs with it. But so far I can't get any further response out of it. I know it will need to negotiate the protocol, and I might be able to work through that. But after it connects (pairs) it just doesn't respond at all. I suspect I'm missing some initial setup thing.
 
Let me get a working version again...I had it working, then started to make some changes and it stopped working. And OF COURSE I haven't put any of this into github so I can't even rollback to a clean commit. Negotiating the protocol was a key step to getting everything talking, though.

I didn't touch the initialize function, so it should still work. We lost internet at the house for the better part of 2 weeks and I'm not nearly good enough to know all these classes & APIs without a lot of Claude, so I've already forgotten a lot of what I had figured out. Use at your own risk.

C++:
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;
}
 
  • Like
Reactions: joelachr
Thanks, that's exactly what I was looking for. I found a few similar-ish examples online, but this one looks clearer. :D

I'll pound on it some more mañana.
 
  • Like
Reactions: hear
Should we start a new thread just for this project? I don’t want to further muck up the original idea here.
 
@hear, can I get a copy of your sendCommand() routine and the value of PID_DELAY?
Thanks!

Here's the whole thing. I must stress to you that I'm 90% sure it isn't working as-is.

C++:
#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;
}
 
Thanks again!

I understand it's probably not working, but I might pick up some tidbits from it. I think I'll be okay for the rest of the code, I have some examples. I just need to get that initialization stuff going.
 
@hear, your initialization code did it! It sets a protocol, and I can read values like RPM and coolant temp.

I had to move the J1850 VPW (aka KWP2000) protocol for my 2005 up to try first, because it wouldn't connect if it tried the ISO 9141-2 for your 1997 first. Nor on Auto. The code looks like it should work for each protocol, but no. I'll experiment with that stuff some more.

So far it's just my initial messy code plus your init code. Now to turn it into something useful...

Good idea about starting another thread, if I (or you) get far enough along to have something worth sharing.
 
just something I found - I was thinking about going ahead and using bluetooth for the ECT control and then falling back on LCT control (which only requires local sensors) if the comms failed. It would save me a bit of wiring and not have to get into the vehicle harness at all, but Bluetooth has turned out to not be an option because reasons:

1. The ELMduino library I'm using supports Bluetooth "Classic" but doesn't support BLE (Bluetooth Low Energy)
2. The Arduino Nano ESP32 has an ESP32-S3 chip, which only supports BLE and does not support Bluetooth Classic.

From what I've read the o.g. ESP32 supported both, which is what led me down a few hours of wasted research time full of example code where people clearly used the bluetooth classic library on an ESP32 chip. It was also confounded by the fact that someone contributed a bluetooth library named exactly the same as the official Arduino library, so I had installed and included that one and kept getting compile errors trying to use functions that didn't exist in that library.

I haven't completely abandoned the idea of a wireless method of interfacing with the ELM327/OBD2...if I got a Wifi OBD2 scanner I could wifi to it. I'm gonna do a little research and see if any of the wifi Elm327 modules will support multiple devices on them. That would make it so I could have a cooling fan AND run the Torque app at the same time.

I haven't heard anything from Sparkfun on the UART - OBD2 board. I ordered an OBD2 reader with an RS232 interface on a DB9 connector. I'm still going to try to intercept the UART pins directly but I'm not the most skilled solderer so if the traces are concealed or just too small for me to work with, I can get a TTL - RS232 converter board at $10 for a pack of 3.

I see SparkFun has the UART-OBDII boards in stock. Have you received yours, and have you been able to get it working?

I saw one review on SparkFun where the guy got all of the protocols to work on various vehicles, except for J1850 VPW, which is what we need. I might buy one if I can be sure that protocol will work on it.
 
I see SparkFun has the UART-OBDII boards in stock. Have you received yours, and have you been able to get it working?

I saw one review on SparkFun where the guy got all of the protocols to work on various vehicles, except for J1850 VPW, which is what we need. I might buy one if I can be sure that protocol will work on it.

I got it but I haven't messed with it yet. That would be unfortunate if it doesn't work for our protocol.
 
I got it but I haven't messed with it yet. That would be unfortunate if it doesn't work for our protocol.

I tried the cheap Veepeak Bluetooth one. I got it working, talking with an ESP32. When it works, it's fine. But Bluetooth can be flaky. Maybe it's just this cheap device. It would send a string of gobbledygook once in a while, and sometimes it wouldn't send the right connection values. I got sick of writing lots of code just to verify its working right. Sometimes it required a power cycle before it would behave. I'm about done with Bluetooth.

So I'm thinking about a UART device, but I've only seen the SparkFun one and one other, which seems very similar and costs a bit more.

I even looked inside my USB OBDII device to see if I could bypass the FTDI TTL-to-USB, but it uses the tiniest 0.5mm pitch surface mount chips.