Question about manually parsing responses.
Apologies first. I am trying to learn C++, bluetooth serial, OBD2 command and response structure all at the same time and I'm sure what I'm missing is something fundamental and simple.
I'm trying to extract transmission temperature, proving to be easy on a 2018 CAN Silverado and harder on 2001 VPW Silverado. For the 2018 Silverado, I can set header to 7E2 and
tts = (((myELM327.processPID(34, 6464, 1, 1, 1, 0) - 40) * 1.8) + 32);
works perfectly. The truck takes 221940 and returns 621940XX (with XX being the single data byte).
However on the older VPW truck, the command is actually 22194001, but it still only returns 62 19 40 XX. When I try to use processPID on that one, it seems like the processPID is getting confused by the missing 4th byte (01) in the return. I apologize that I don't have a debug print from this, I only have access to that truck periodically.
I figured I could just use sendCommand and parse the response myself, but I'm missing something here.
The following code was written for the 2018 CAN vehicle and is essentially a strict adaptation of one of the examples:
#include "BluetoothSerial.h"
#include "ELMduino.h"
#define BT_DISCOVER_TIME 10000
esp_spp_sec_t sec_mask = ESP_SPP_SEC_NONE;
esp_spp_role_t role = ESP_SPP_ROLE_MASTER;
uint8_t address[6] = { 0x00, 0x1D, 0xA5, 0x06, 0xCC, 0x40 }; // Change this to the MAC address of your bluetooth ELM327 adapter
BluetoothSerial SerialBT;
#define ELM_PORT SerialBT
#define DEBUG_PORT Serial
ELM327 myELM327;
int nb_query_state = SEND_COMMAND; // Set the inital query state ready to send a command
void setup() {
DEBUG_PORT.begin(9600);
myELM327.begin(ELM_PORT, true, 2000);
ELM_PORT.begin("ArduHUD", true);
if (!ELM_PORT.connect(address, sec_mask, role)) {
Serial.println("Couldn't connect to ELM327 device.");
while (1)
;
}
if (!myELM327.begin(ELM_PORT, true, 2000)) {
Serial.println("ELM327 device couldn't connect to ECU.");
while (1)
;
}
Serial.println("Connected to ELM327");
myELM327.sendCommand_Blocking("ATSH 7E2"); // Set a custom header using ATSH command.
}
void loop() {
if (nb_query_state == SEND_COMMAND) // We are ready to send a new command
{
myELM327.sendCommand("221940"); // Send the custom PID commnad
nb_query_state = WAITING_RESP; // Set the query state so we are waiting for response
} else if (nb_query_state == WAITING_RESP) // Our query has been sent, check for a response
{
myELM327.get_response(); // Each time through the loop we will check again
}
if (myELM327.nb_rx_state == ELM_SUCCESS) // Our response is fully received, let's get our data
{
int A = myELM327.payload[6]; // Parse the temp value A from the response
Serial.println(A); // Print the returned value
nb_query_state = SEND_COMMAND; // Reset the query state for the next command
delay(5000); // Wait 5 seconds until we query again
}
else if (myELM327.nb_rx_state != ELM_GETTING_MSG) { // If state == ELM_GETTING_MSG, response is not yet complete. Restart the loop.
nb_query_state = SEND_COMMAND; // Reset the query state for the next command
myELM327.printError();
delay(5000); // Wait 5 seconds until we query again
}
}
When I run this, the debug print shows that the command is being sent properly, and the return is appropriate and correct, but when I ask it to Serial.println(A) it gives me the integer 52, and for the life of me I can't figure out why.
Clearing input serial buffer
Sending the following command/query: 221940
Received char: 6
Received char: 2
Received char: 1
Received char: 9
Received char: 4
Received char: 0
Received char: 4
Received char: 9
Received char: \r
Received char: \r
Received char: >
Delimiter found.
All chars received: 62194049
52
I've tried poking through the .h and .cpp files for clues on how the payload works, but this is where my lack of knowledge shows up. Looking for some insight on where I'm going wrong.
I don't have a debug, but this is from the putty session when I was logged into the older VPW truck, just showing the header and the response to the command. 68 in the response was the correct temperature at the time of the request.
>atsh 6c10f1
OK
>221940
7F 22 19 40 12
>22194001
62 19 40 68
Try: int A = myELM327.response_A;
error: 'class ELM327' has no member named 'response_A'; did you mean 'response'?
version 3.4.1
I found this in the .cpp...
// Assign extracted values to response_A, response_B, ..., response_H safely
response_A = extractedBytes[0];
response_B = extractedBytes[1];
response_C = extractedBytes[2];
response_D = extractedBytes[3];
response_E = extractedBytes[4];
response_F = extractedBytes[5];
response_G = extractedBytes[6];
response_H = extractedBytes[7];
I changed it to int A = response_A; that compiled successfully but just returned zero.
if it's not obvious, I'm just sort of guessing.
Oh, my bad, try this instead: int A = myELM327.responseByte_0;
int A = myELM327.responseByte_0;
(I copy/pasted the command from my code and not your response to typo-check myself) still just returns zero when I serial.print(A)
Clearing input serial buffer
Sending the following command/query: 221940
Received char: 6
Received char: 2
Received char: 1
Received char: 9
Received char: 4
Received char: 0
Received char: 4
Received char: 8
Received char: \r
Received char: \r
Received char: >
Delimiter found.
All chars received: 62194048
0
I'm curious what you get if you print response byte 0 through 7. I imagine you can use those variables to process the data you need. Sorry for the difficulty in help - it's been a while since I've messed with this lib
Rats. Still all zeros.
if (myELM327.nb_rx_state == ELM_SUCCESS) // Our response is fully received, let's get our data
{
int A = myELM327.responseByte_0; // Parse the temp value A from the response
int B = myELM327.responseByte_1;
int C = myELM327.responseByte_2;
int D = myELM327.responseByte_3;
int E = myELM327.responseByte_4;
int F = myELM327.responseByte_5;
int G = myELM327.responseByte_6;
int H = myELM327.responseByte_7;
Serial.print("Byte 0: "); // Print the returned value
Serial.println(A);
Serial.print("Byte 1: ");
Serial.println(B);
Serial.print("Byte 2: ");
Serial.println(C);
Serial.print("Byte 3: ");
Serial.println(D);
Serial.print("Byte 4: ");
Serial.println(E);
Serial.print("Byte 5: ");
Serial.println(F);
Serial.print("Byte 6: ");
Serial.println(G);
Serial.print("Byte 7: ");
Serial.println(H);
nb_query_state = SEND_COMMAND; // Reset the query state for the next command
delay(5000); // Wait 5 seconds until we query again
}
Clearing input serial buffer
Sending the following command/query: 221940
Received char: 6
Received char: 2
Received char: 1
Received char: 9
Received char: 4
Received char: 0
Received char: 5
Received char: 9
Received char: \r
Received char: \r
Received char: >
Delimiter found.
All chars received: 62194059
Byte 0: 0
Byte 1: 0
Byte 2: 0
Byte 3: 0
Byte 4: 0
Byte 5: 0
Byte 6: 0
Byte 7: 0
I know it has to be in there somewhere, debug printed it after "All chars received: ". I started this project with the goal of learning more about C++ along the way; I should do some more of that.
By the way, please no apologies about trouble helping -- this is a very small part of a bigger project and I'd be absolutely nowhere on it without your library.
This feels like it can't be the right way to do it, but it DOES give me an integer with only the last byte of the response. I'm tinkering with ways to get that last byte from hex into dec. I must be missing something simple.
int pl = atoi(myELM327.payload);
Serial.print("Test payload: ");
Serial.println(pl);
int ttsHEX = (pl - 62194000);
Serial.print("Hex: ");
Serial.println(ttsHEX);
edit: yeah, it's not the right way. Thinking about it, this will fail when it gives me 6219404B. Back to the drawing board.
For processing the payload, you can probably do something like this:
uint8_t ctoi(uint8_t value)
{
if (value >= 'A')
return value - 'A' + 10;
else
return value - '0';
}
int A = ((int)ctoi(myELM327.payload[0]) | ((int)ctoi(myELM327.payload[1]) << 4)) & 0xFF;
I was fooling around with an online C++ simulator during some downtime at work and came up with this. It's converting the c-style payload string into a c++ string, then deleting all but the byte I care about, then converting that to a decimal integer and doing all the math to convert it to Fahrenheit. I don't really know whether this is an inefficient or otherwise dumb technique, but it gives me the correct answer in testing.
char* payload = myELM327.payload;
std::string strPay(payload);
strPay.erase(0, 6);
int ttsF = (((std::stoul(strPay, nullptr, 16) - 40) * 1.8) + 32);
Serial.print("Temp F: ");
Serial.println(ttsF);
I came up with it earlier this evening before I saw your response above. My knowledge level is going to need some work before I can even understand your snippet.