CANMessage documentation?

So I got my carloop hooked up to my Impreza WRX and have been able to get the raw CAN messages. I’ve looked into the carloop library and it seems like it’s configured using canDriver which then results in my loop function getting CANMessage’s. I can see in one of the examples a small listing of hex codes to messages. Does anyone know where there might be documentation about the canDriver in use and a good way to debug/log out what CANMessage’s that I am receiving?

I’m obviously just getting started here and have a long way to go. Any pointers to good documentation would be incredibly helpful. I need to get the data in a readable format, right now I’ve been able to get a log of all the message ID’s but I don’t know how to make heads or tails of the raw data yet.

My ultimate goal is to have my cell phone provide a wifi signal which is running a webserver that the particle can connect with so that i’d be able to view live stats about the car while it’s on the road. An alternative to this would be picking up an electron or some other board with bluetooth.

Glad to know it is working for you! Can you share a sample of the raw data that you are seeing?

Happily! To get this data I took the carloop minimal example – carloop_minimal.cpp

And modified the updateCanMessageCount to match what I have here. I’m unsure if I’m even outputting the data in the right formats. This is the recording from nothing to starting up the car. I can always collect more data if that’s useful.

void updateCanMessageCount()
{
    CANMessage message;
    while(carloop.can().receive(message))
    {
      Serial.printf("ID:\tBit 0\tBit 1\t Bit 2\tBit 3\tBit 4\tBit 5\tBit 6\tBit 7\tBit 8");
      Serial.println("");
      Serial.print(message.id, HEX);
      Serial.print("\t");
      Serial.print(message.data[0], HEX);
      Serial.print("\t");
      Serial.print(message.data[1], HEX);
      Serial.print("\t");
      Serial.print(message.data[2], HEX);
      Serial.print("\t");
      Serial.print(message.data[3], HEX);
      Serial.print("\t");
      Serial.print(message.data[4], HEX);
      Serial.print("\t");
      Serial.print(message.data[5], HEX);
      Serial.print("\t");
      Serial.print(message.data[6], HEX);
      Serial.print("\t");
      Serial.print(message.data[7], HEX);
      Serial.print("\t");
      Serial.print(message.data[8], HEX);
      Serial.println("\tEND");
      canMessageCount++;
    }
}
ID:	Bit 0	Bit 1	 Bit 2	Bit 3	Bit 4	Bit 5	Bit 6	Bit 7	Bit 8
410	13	0	2D	15	0	A7	7	C1	53
411	5	FF	7F	0	1	0	10	4	53
412	0	82	1	82	1	19	80	84	53
601	8	DA	0	3C	0	64	0	0	53
2	C	80	EA	0	0	0	0	1	53
514	0	0	77	4	CC	19	7	0	53
620	30	0	3	0	0	0	0	0	53
75	0	0	0	0	0	8	6	1E	53
513	0	0	0	0	0	0	0	0	53
512	F0	F0	0	0	90	46	42	40	53
511	0	0	0	0	0	0	0	4	53
70	F2	7F	0	FF	7C	80	6	D3	53
80	F2	7F	0	0	B8	7F	6	92	53
501	0	0	0	FF	0	47	0	0	53
576	0	0	0	0	14	13	26	BD	53
410	13	0	2D	15	0	AD	7	C1	53
411	5	FF	7F	0	1	0	10	4	53
412	0	83	1	83	1	19	80	84	53
600	0	F3	7	3E	22	2A	4	0	53
601	8	DA	0	3C	0	64	0	0	53
2	C	80	FB	0	0	0	0	1	53
410	13	0	2D	15	0	AD	7	C1	53
411	5	FF	7F	0	1	0	10	4	53
412	0	83	1	83	1	19	80	84	53
601	8	DA	0	3C	0	64	0	0	53
2	C	80	8C	0	0	0	0	1	53
514	0	0	77	4	CC	19	9	0	53
620	30	0	3	0	0	0	0	0	53
75	0	0	0	0	0	8	7	3	53
513	0	0	0	0	0	0	0	0	53
512	F0	F0	0	0	90	47	42	40	53
511	0	0	0	0	0	0	0	4	53
70	FD	7F	0	FF	63	80	7	C	53
80	1B	80	0	0	C5	7F	7	D4	53
501	0	0	0	FF	0	48	0	0	53
576	0	0	0	0	16	3	27	17	53
410	13	0	2D	15	0	A0	7	C1	53
411	5	FF	BF	0	1	0	10	4	53
412	0	81	1	81	1	19	80	84	53
601	8	DA	0	3C	0	64	0	0	53
2	C	80	9D	0	0	0	0	1	53
410	13	0	2D	15	0	A7	7	C1	53
411	5	FF	BF	0	1	0	10	4	53
412	0	80	1	80	1	19	80	84	53
601	8	DA	0	3C	0	64	0	0	53
2	C	80	AE	0	0	0	0	1	53
514	0	0	77	4	CC	19	B	0	53
620	30	0	3	0	0	0	0	0	53
75	0	0	0	0	0	8	8	B8	53
513	0	0	0	0	0	0	0	0	53
512	F0	F0	0	0	90	48	42	40	53
511	0	0	0	0	0	0	0	4	53
70	3	80	0	FF	A0	80	8	3A	53
80	5	80	0	0	B9	7F	8	95	53
501	0	0	0	FF	0	49	0	0	53
576	0	0	0	0	18	8	28	2B	53
410	13	0	2D	15	0	A7	7	C1	53
411	5	FF	BF	0	1	0	10	4	53
412	0	80	1	80	1	19	80	84	53
601	8	DA	0	3C	0	64	0	0	53
2	C	80	BF	0	0	0	0	1	53
410	13	0	2D	15	0	95	7	C1	53
411	5	FF	BF	0	1	0	10	4	53
412	0	7F	1	7F	1	19	80	84	53
600	0	DD	7	3E	23	2A	4	0	53
601	8	DA	0	3B	0	64	0	0	53
2	C	80	40	0	0	0	0	1	53
514	0	0	77	4	CC	19	D	0	53
620	30	0	3	0	0	0	0	0	53
75	0	0	0	0	0	8	9	A5	53
513	0	0	0	0	0	0	0	0	53
512	F0	F0	0	0	90	49	42	40	53
511	0	0	0	0	0	0	0	4	53
70	FA	7F	0	FF	AD	80	9	8D	53
80	E9	7F	0	0	B1	7F	9	26	53
501	0	0	0	FF	0	4A	0	0	53
576	0	0	0	0	1A	33	29	7A	53
410	13	0	2D	15	0	9E	7	C1	53
411	5	FF	BF	0	1	0	10	4	53
412	0	7F	1	7F	1	19	80	84	53
601	8	DA	0	3B	0	64	0	0	53
2	C	80	51	0	0	0	0	1	53
410	13	0	2D	15	0	9E	7	C1	53
411	5	FF	BF	0	1	0	10	4	53
412	0	80	1	80	1	19	80	84	53
601	8	DA	0	3B	0	64	0	0	53
2	C	80	62	0	0	0	0	1	53
514	0	0	77	4	8C	19	F	0	53
620	30	0	3	0	0	0	0	0	53
75	0	0	0	0	0	8	A	82	53
513	0	0	0	0	0	0	0	0	53
512	F0	F0	0	0	90	4A	42	40	53
511	0	0	0	0	0	0	0	4	53
70	FA	7F	0	FF	AF	80	A	A9	53
80	9	80	0	0	A5	7F	A	6	53
501	0	0	0	FF	0	4B	0	0	53
576	0	0	0	0	1C	7	2A	B4	53
410	13	0	2D	15	0	8B	7	C1	53
411	5	FF	BF	0	1	0	10	4	53
412	0	80	1	80	1	19	80	84	53

Good news! I just got an output of my cars RPM using the other example provided. I’m not entirely sure what the other OBD-II data that I’m getting above is but I understand now how to communicate and get messages off the bus.

:slight_smile: Awesome.

You got so far already. This is awesome!

In addition to the id and data you’ll want to print the message length (number of data bytes that actually have data inside).

The docs for the CAN driver is here:
https://docs.particle.io/reference/firmware/photon/#can-canbus-

For the server, you can create a TCPServer https://docs.particle.io/reference/firmware/photon/#tcpserver
A good place to check for examples of using the TCPServer is the Particle community at https://community.particle.io

Those CAN driver docs are exactly what I was looking for. This is my second day now working with c++ and the photon so it’s all a little greek for me still. Now that I can read data from the car the next step is exactly that, setting up the TCPServer and seeing if I can get data to the phone live.

First steps will be a simple tool to display live car stats via ODB-II.

This was much easier than I was expecting, I hope the TCPServer stuff goes just as well!

I would first try @jvanier suggestion using the TCPserver documentation. If you want to use a board with bluetooth, we recommend the Redbear Duo. It has a microcontroller with bluetooth radio, a CAN bus, and is compatible with Carloop’s pinout. You can re-use your code from Particle because it uses Particle’s WEB IDE.

@samueldavid,

Did you fix up your code according to the suggestions in this thread?
https://community.carloop.io/t/problems-whit-carloop-how-to-start-read-cam-bus/638

If you think you have those problems solved, please go back to that thread and post your updated code. I am guessing you still need to work on your code a bit more before you get messages that are meaningful. Once you get some good messages, then we can start decoding them.

Need help, just received my Carloop and Electron. I download the program to read OBD2 data and because I’m new to this, I can’t make heads or tails of the output. I’ve looked at the Wiki reference, and it’s still greek to me. Can someone read this and at least point me in the right direction. i.e., event: m data: {“data”:“1570.0:05000007fd,1570.0:000010ff00ff0000,1570.0:ff035b0402006910,1570.0:0000000000000000,1570.0:45320c0b32310071,1570.0:fa6e000000460000,1570.0:1d3233300e836b3d,1570.0:86b47f0c11200018,1570.0:dcad008700000000,1570.0:0000,1570.0:1000000000000000,1570.0:00”,“ttl”:60,“published_at”:“2018-08-07T21:16:56.149Z”,“coreid”:"XXXXX}

What I’m looking for just a little help with understanding something like “1570.0:05000007fd”, how do I read this?

@ruhandsomeiii,

You will need to tell us which program you downloaded, since it will likely be necessary to read the code to see what this output is. Without any description, it is difficult to say.

Thanks for your reply. I downloaded the Carloop OBD Publisher on GitHub. I included it below, the code appears to work, I just can’t understand how to decode it using the Wiki

#include “application.h”
#include “carloop.h”
#include “base85.h”

SYSTEM_MODE(SEMI_AUTOMATIC);
SYSTEM_THREAD(ENABLED);

void sendObdRequest();
void waitForObdResponse();
void delayUntilNextRequest();
void printValuesAtInterval();
void printValues();
String dumpMessage(const CANMessage &message);
bool byteArray8Equal(uint8_t a1[8], uint8_t a2[8]);

Carloop carloop;

int canMessageCount = 0;

// OBD CAN message IDs
const auto OBD_CAN_BROADCAST_ID = 0X7DF;
const auto OBD_CAN_REQUEST_ID = 0x7E0;
const auto OBD_CAN_REPLY_ID_MIN = 0x7E8;
const auto OBD_CAN_REPLY_ID_MAX = 0x7EF;

// OBD services / modes
const auto OBD_MODE_CURRENT_DATA = 0x01;

// OBD PIDs
const auto OBD_PID_SUPPORTED_PIDS_01_20 = 0x00;
// MIL = malfunction indicator lamp = check engine light
const auto OBD_PID_MIL_STATUS = 0x01;
const auto OBD_PID_FUEL_SYSTEM_STATUS = 0x03;
const auto OBD_PID_ENGINE_LOAD = 0x04;
const auto OBD_PID_COOLANT_TEMPERATURE = 0x05;
const auto OBD_PID_SHORT_TERM_FUEL_TRIM = 0x06;
const auto OBD_PID_LONG_TERM_FUEL_TRIM = 0x07;
const auto OBD_PID_ENGINE_RPM = 0x0c;
const auto OBD_PID_VEHICLE_SPEED = 0x0d;
const auto OBD_PID_TIMING_ADVANCE = 0x0e;
const auto OBD_PID_INTAKE_AIR_TEMPERATURE = 0x0f;
const auto OBD_PID_MAF_AIR_FLOW_RATE = 0x10;
const auto OBD_PID_THROTTLE = 0x11;
const auto OBD_PID_O2_SENSORS_PRESENT = 0x13;
const auto OBD_PID_O2_SENSOR_2 = 0x15;
const auto OBD_PID_OBD_STANDARDS = 0x1c;
const auto OBD_PID_ENGINE_RUN_TIME = 0x1f;
const auto OBD_PID_SUPPORTED_PIDS_21_40 = 0x20;
const auto OBD_PID_DISTANCE_TRAVELED_WITH_MIL_ON = 0x21;
const auto OBD_PID_COMMANDED_EVAPORATIVE_PURGE = 0x2e;
const auto OBD_PID_FUEL_TANK_LEVEL_INPUT = 0x2f;
const auto OBD_PID_WARM_UPS_SINCE_CODES_CLEARED = 0x30;
const auto OBD_PID_DISTANCE_TRAVELED_SINCE_CODES_CLEARED = 0x31;
const auto OBD_PID_ABSOLUTE_BAROMETRIC_PRESSURE = 0x33;
const auto OBD_PID_O2_SENSOR_1 = 0x34;
const auto OBD_PID_CATALYST_TEMPERATURE_BANK1_SENSOR1 = 0x3c;
const auto OBD_PID_SUPPORTED_PIDS_41_60 = 0x40;
const auto OBD_PID_MONITOR_STATUS = 0X41;
const auto OBD_PID_CONTROL_MODULE_VOLTAGE = 0X42;
const auto OBD_PID_ABSOLUTE_LOAD_VALUE = 0X43;
const auto OBD_PID_FUEL_AIR_COMMANDED_EQUIV_RATIO = 0X44;
const auto OBD_PID_RELATIVE_THROTTLE = 0X45;
const auto OBD_PID_AMBIENT_AIR_TEMPERATURE = 0X46;
const auto OBD_PID_ABSOLUTE_THROTTLE_B = 0X47;
const auto OBD_PID_ACCELERATOR_PEDAL_POSITION_D = 0X49;
const auto OBD_PID_ACCELERATOR_PEDAL_POSITION_E = 0X4a;
const auto OBD_PID_COMMANDED_THROTTLE_ACTUATOR = 0X4c;

const size_t NUM_PIDS_TO_REQUEST = 30;
const uint8_t pidsToRequest[NUM_PIDS_TO_REQUEST] = {
OBD_PID_ENGINE_LOAD,
OBD_PID_COOLANT_TEMPERATURE,
OBD_PID_SHORT_TERM_FUEL_TRIM,
OBD_PID_LONG_TERM_FUEL_TRIM,
OBD_PID_ENGINE_RPM,
OBD_PID_VEHICLE_SPEED,
OBD_PID_TIMING_ADVANCE,
OBD_PID_INTAKE_AIR_TEMPERATURE,
OBD_PID_MAF_AIR_FLOW_RATE,
OBD_PID_THROTTLE,
OBD_PID_O2_SENSOR_2,
OBD_PID_ENGINE_RUN_TIME,
OBD_PID_DISTANCE_TRAVELED_WITH_MIL_ON,
OBD_PID_COMMANDED_EVAPORATIVE_PURGE,
OBD_PID_FUEL_TANK_LEVEL_INPUT,
OBD_PID_WARM_UPS_SINCE_CODES_CLEARED,
OBD_PID_DISTANCE_TRAVELED_SINCE_CODES_CLEARED,
OBD_PID_ABSOLUTE_BAROMETRIC_PRESSURE,
OBD_PID_O2_SENSOR_1,
OBD_PID_CATALYST_TEMPERATURE_BANK1_SENSOR1,
OBD_PID_MONITOR_STATUS,
OBD_PID_CONTROL_MODULE_VOLTAGE,
OBD_PID_ABSOLUTE_LOAD_VALUE,
OBD_PID_FUEL_AIR_COMMANDED_EQUIV_RATIO,
OBD_PID_RELATIVE_THROTTLE,
OBD_PID_AMBIENT_AIR_TEMPERATURE,
OBD_PID_ABSOLUTE_THROTTLE_B,
OBD_PID_ACCELERATOR_PEDAL_POSITION_D,
OBD_PID_ACCELERATOR_PEDAL_POSITION_E,
OBD_PID_COMMANDED_THROTTLE_ACTUATOR
};
uint8_t pidIndex = NUM_PIDS_TO_REQUEST - 1;

String dumpForPublish;

auto *obdLoopFunction = sendObdRequest;
unsigned long transitionTime = 0;
uint8_t lastMessageData[8];

void setup() {
Serial.begin(115200);
carloop.begin();
Particle.connect();
transitionTime = millis();
}

void loop() {
carloop.update();
printValuesAtInterval();
obdLoopFunction();
}

/*************** Begin: OBD Loop Functions ****************/

void sendObdRequest() {
pidIndex = (pidIndex + 1) % NUM_PIDS_TO_REQUEST;

CANMessage message;
message.id = OBD_CAN_BROADCAST_ID;
message.len = 8; // just always use 8
message.data[0] = 0x02; // 0 = single-frame format, 2  = num data bytes
message.data[1] = OBD_MODE_CURRENT_DATA; // OBD MODE
message.data[2] = pidsToRequest[pidIndex]; // OBD PID

carloop.can().transmit(message);

obdLoopFunction = waitForObdResponse;
transitionTime = millis();

}

void waitForObdResponse() {
if (millis() - transitionTime >= 100) {
obdLoopFunction = delayUntilNextRequest;
transitionTime = millis();
return;
}

String dump;
CANMessage message;
while (carloop.can().receive(message)) {
	canMessageCount++;
	if (message.id == 0x130) {
		// This message gets sent every tenth of a second,
		// so only publish it when the value changes.
		if (!byteArray8Equal(message.data, lastMessageData)) {
			memcpy(lastMessageData, message.data, 8);
			dump += dumpMessage(message);
		}
	} else {
		dump += dumpMessage(message);
	}
}

Serial.write(dump);
dumpForPublish += dump;
// change to:
// if > 196 binary bytes
// when each time stamp is exactly 2 bytes encoding integer tenths of a second
// and 1-5 data bytes follow
// then we base85 encode the binary to get close to the 255-byte publish limit
if (dumpForPublish.length() >= 220) {
	Particle.publish("m", dumpForPublish, 60, PRIVATE);
	dumpForPublish.remove(0);
}

}

void delayUntilNextRequest() {
if (millis() - transitionTime >= 80) {
obdLoopFunction = sendObdRequest;
transitionTime = millis();
}
}

/*************** End: OBD Loop Functions ****************/

void printValuesAtInterval() {
static const unsigned long interval = 20000;
static unsigned long lastDisplay = 0;
if (millis() - lastDisplay < interval) {
return;
}
lastDisplay = millis();
printValues();
}

void printValues() {
Serial.printf("Battery voltage: %12f ", carloop.battery());
Serial.printf(“CAN messages: %12d “, canMessageCount);
Serial.println(””);
}

String dumpMessage(const CANMessage &message) {
// change to: each timestamp as exactly 2 bytes
// as unsigned integer tenths of a second
// wrap around carefully
// maybe (quick stab) – uint16_t t = (millis() / 100) & 0xffff
String str = String::format(“%.1f:”, millis() / 1000.0);
int startIdx = 0;
int lastIdx = message.len - 1;
if (message.id >= 0x700) {
// We can ignore the first two bytes
// data[0] is the length
// data[1] is the UDS service response ID, always 0x41 for me
// data[2] is important to include - it’s the PID
startIdx = 2;
if (message.data[0] < message.len) {
lastIdx = message.data[0];
}
}
// hmm… Since the binary data can include zeros
// I may have to do the base85 encoding on the fly
// rather than right before publish…
for (int i = startIdx; i <= lastIdx; i++) {
str += String::format(“%02x”, message.data[i]);
}
str += “,”;
return str;
}

bool byteArray8Equal(uint8_t a1[8], uint8_t a2[8]) {
for (int i = 0; i < 8; i++) {
if (a1[i] != a2[i]) return false;
}
return true;
}

Just a side note, the code you posted does not appear to be exactly the same as what I found on GitHub here:

Thanks for posting the code. Now I know what you are asking about.

The part before the “:” is the timestamp. Your device had been running for 1570.0 seconds when it received the CAN message. I find it rather bizarre that all the messages have the same timestamp. That suggests an error in the code.

After the “:” are the bytes of the message starting with the PID
“05” is the PID.
You can look up the PIDs here:

According to the chart, PID 05 is the engine coolant temperature and has 1 additional byte of data.
The next byte is “00”. If you do the calculation in the chart 00 -40 = -40 deg C.
That does not really make sense.
Another clue that there is something wrong with your code is that the message should stop after one extra byte, something like this:
“1570.0:0500”

By the way, are you checking the USB Serial line to see if you are getting the battery voltage and the number of messages? That should give you some more clues if things are working.

Thank you for the reply. It does look like there may be something off in the code. With the code I posted, I only removed the web links in the comments. The rest of the code was as I downloaded it. However, I may well have copied it wrong or something. The next step will be to use the USB Serial line. Data that looks like “1570.0:0500”, would definitely make things easier to decode. I’m trying to do this with a 2012 KIA Sorento. I know there is plenty of OBD2 data because I use Torque on a Samsung tablet and connect via a ELM 327 Bluetooth device, and it works fine. I’ll re-download the code and try it again. Thanks for your tips and help.