Yes we did achieve this. We provide automotive related embedded solution to various organizations and this had to be done to facilitate logistics department of an organization. We also designed a mobile app and a computer program linked with google's API to display the data either on cellphone or a server. I am attaching a few photos of that.
It wasn't solely my work and it will be unethical of me to just handover the code. However, following are some partial details that can help you.
We used ATMEGA 2560 processor. I2C protocol was used to communicate with ELM27 OBD II reader. Individual component code was written in C++ and then included to Arduino as a header file and main code was written in arduino's environment. I am posting below the C++ code for just the OBD part. we had 2 files obd.h and obd.cpp
OBD.h
define OBD_TIMEOUT_SHORT 2000 /* ms */
define OBD_TIMEOUT_LONG 7000 /* ms */
define OBD_SERIAL_BAUDRATE 38400
define OBD_RECV_BUF_SIZE 128
ifndef OBDUART
if defined(AVR_ATmega32U4) || defined(AVR_ATmega2560) || defined(AVR_ATmega1280) || defined(AVR_ATmega644P)
define OBDUART Serial1
else
define OBDUART Serial
endif
endif
// mode 0 pids
define PID_RPM 0x0C
define PID_SPEED 0x0D
define PID_THROTTLE 0x11
define PID_ENGINE_LOAD 0x04
define PID_COOLANT_TEMP 0x05
define PID_INTAKE_TEMP 0x0F
define PID_MAF_FLOW 0x10
define PID_ABS_ENGINE_LOAD 0x43
define PID_AMBIENT_TEMP 0x46
define PID_FUEL_PRESSURE 0x0A
define PID_INTAKE_MAP 0x0B
define PID_BAROMETRIC 0x33
define PID_TIMING_ADVANCE 0x0E
define PID_FUEL_LEVEL 0x2F
define PID_RUNTIME 0x1F
define PID_DISTANCE 0x31
// states
define OBD_DISCONNECTED 0
define OBD_CONNECTING 1
define OBD_CONNECTED 2
unsigned int hex2uint16(const char *p);
unsigned char hex2uint8(const char *p);
class COBD
{
public:
COBD():dataMode(1),errors(0),m_state(OBD_DISCONNECTED) {}
virtual void begin();
virtual bool init(bool passive = false);
virtual bool read(byte pid, int& result, bool passive = false);
virtual void sleep(int seconds);
// Query and GetResponse for advanced usage only
virtual void sendQuery(byte pid);
bool isValidPID(byte pid);
byte getState() { return m_state; }
byte dataMode;
byte errors;
byte pidmap[4 * 4];
byte vin[17];
protected:
virtual char* getResponse(byte& pid, char* buffer);
virtual bool getResponseParsed(byte& pid, int& result);
virtual byte receive(char* buffer);
virtual bool available();
virtual char read();
virtual void write(char* s);
virtual void write(char c);
virtual void dataIdleLoop() {}
void debugOutput(const char* s);
int normalizeData(byte pid, char* data);
byte m_state;
private:
virtual uint8_t getPercentageValue(char* data)
{
return (uint16_t)hex2uint8(data) * 100 / 255;
}
virtual uint16_t getLargeValue(char* data)
{
return hex2uint16(data);
}
virtual uint8_t getSmallValue(char* data)
{
return hex2uint8(data);
}
virtual int16_t getTemperatureValue(char* data)
{
return (int)hex2uint8(data) - 40;
}
};
define I2C_ADDR 0x62
define MAX_PAYLOAD_SIZE 32
define CMD_QUERY_STATUS 0x10
define CMD_SEND_COMMAND 0x11
define CMD_QUERY_DATA 0x12
define CMD_UART_BEGIN 0x13
define CMD_UART_SEND 0x14
define CMD_UART_RECV 0x15
typedef struct {
uint32_t time;
uint16_t pid;
float value;
} PID_INFO;
typedef struct {
uint16_t time;
uint8_t message;
uint8_t data;
} COMMAND_BLOCK;
class COBDI2C : public COBD {
public:
void begin(byte addr = I2C_ADDR);
bool init();
bool read(byte pid, int& result, bool passive = false);
void write(char* s);
// Bluetooth communication API
bool btInit(uint16_t baudrate = 9600);
bool btSend(byte* data, byte length);
bool btReceive(byte* buffer, byte bufsize);
private:
bool sendCommand(byte cmd, uint8_t data = 0, byte* payload = 0, byte payloadBytes = 0);
byte receive(char* buffer);
byte m_addr;
};
OBD.cpp
include <Wire.h>
void COBD::sendQuery(unsigned char pid)
{
char cmd[8];
sprintf_P(cmd, s_cmd_fmt, dataMode, pid);
ifdef DEBUG
debugOutput(cmd);
endif
write(cmd);
}
bool COBD::read(byte pid, int& result, bool passive)
{
// send a query command
sendQuery(pid);
// wait for reponse
bool hasData;
unsigned long tick = millis();
do {
dataIdleLoop();
} while (!(hasData = available()) && millis() - tick < OBD_TIMEOUT_SHORT);
if (!hasData) {
errors++;
return false;
}
// receive and parse the response
return getResponseParsed(pid, result);
}
bool COBD::available()
{
return OBDUART.available();
}
char COBD::read()
{
char c = OBDUART.read();
ifdef REDIRECT
REDIRECT.write(c);
endif
return c;
}
void COBD::write(char* s)
{
OBDUART.write(s);
}
void COBD::write(char c)
{
OBDUART.write(c);
}
int COBD::normalizeData(byte pid, char* data)
{
int result;
switch (pid) {
case PID_RPM:
result = getLargeValue(data) >> 2;
break;
case PID_FUEL_PRESSURE:
result = getSmallValue(data) * 3;
break;
case PID_COOLANT_TEMP:
case PID_INTAKE_TEMP:
case PID_AMBIENT_TEMP:
result = getTemperatureValue(data);
break;
case PID_ABS_ENGINE_LOAD:
result = getLargeValue(data) * 100 / 255;
break;
case PID_MAF_FLOW:
result = getLargeValue(data) / 100;
break;
case PID_THROTTLE:
case PID_ENGINE_LOAD:
case PID_FUEL_LEVEL:
result = getPercentageValue(data);
break;
case PID_TIMING_ADVANCE:
result = (getSmallValue(data) - 128) >> 1;
break;
case PID_DISTANCE:
case PID_RUNTIME:
result = getLargeValue(data);
break;
default:
result = getSmallValue(data);
}
return result;
}
char* COBD::getResponse(byte& pid, char* buffer)
{
byte n = receive(buffer);
if (n > 6) {
char *p = buffer;
while ((p = strstr(p, "41 "))) {
p += 3;
byte curpid = hex2uint8(p);
if (pid == 0) pid = curpid;
if (curpid == pid) {
errors = 0;
p += 2;
if (*p == ' ')
return p + 1;
}
};
}
return 0;
}
bool COBD::getResponseParsed(byte& pid, int& result)
{
char buffer[OBD_RECV_BUF_SIZE];
char* data = getResponse(pid, buffer);
if (!data) {
// try recover next time
//write('\r');
return false;
}
result = normalizeData(pid, data);
return true;
}
void COBD::sleep(int seconds)
{
char cmd[MAX_CMD_LEN];
strcpy_P(cmd, s_cmd_sleep);
write(cmd);
if (seconds) {
delay((unsigned long)seconds << 10);
write('\r');
}
}
bool COBD::isValidPID(byte pid)
{
if (pid >= 0x7f)
return false;
pid--;
byte i = pid >> 3;
byte b = 0x80 >> (pid & 0x7);
return pidmap[i] & b;
}
void COBD::begin()
{
OBDUART.begin(OBD_SERIAL_BAUDRATE);
}
byte COBD::receive(char* buffer)
{
unsigned long startTime = millis();
unsigned char n = 0;
int timeout = OBD_TIMEOUT_SHORT;
bool prompted = false;
buffer[0] = 0;
for (;;) {
if (available()) {
char c = read();
if (n > 2 && c == '>') {
// prompt char received
prompted = true;
} else if (n < OBD_RECV_BUF_SIZE - 1) {
buffer[n++] = c;
buffer[n] = 0;
if (strstr(buffer, STR_SEARCHING)) {
strcpy(buffer, buffer + sizeof(STR_SEARCHING));
n -= sizeof(STR_SEARCHING);
timeout = OBD_TIMEOUT_LONG;
}
}
} else if (prompted) {
break;
} else {
if (millis() - startTime > timeout) {
// timeout
return 0;
}
dataIdleLoop();
}
}
return n;
}
bool COBD::init(bool passive)
{
unsigned long currentMillis;
char buffer[OBD_RECV_BUF_SIZE];
m_state = OBD_CONNECTING;
write('\r');
delay(100);
while (available()) read();
for (unsigned char i = 0; i < sizeof(s_initcmd) / sizeof(s_initcmd[0]); i++) {
if (!passive) {
char cmd[MAX_CMD_LEN];
strcpy_P(cmd, s_initcmd[i]);
ifdef DEBUG
debugOutput(cmd);
endif
write(cmd);
}
if (receive(buffer) == 0) {
return false;
}
}
while (available()) read();
// load pid map
memset(pidmap, 0, sizeof(pidmap));
for (byte i = 0; i < 4; i++) {
byte pid = i * 0x20;
sendQuery(pid);
char* data = getResponse(pid, buffer);
if (!data) break;
data--;
for (byte n = 0; n < 4; n++) {
if (data[n * 3] != ' ')
break;
pidmap[i * 4 + n] = hex2uint8(data + n * 3 + 1);
}
}
while (available()) read();
m_state = OBD_CONNECTED;
errors = 0;
return true;
}
ifdef DEBUG
void COBD::debugOutput(const char *s)
{
DEBUG.print('[');
DEBUG.print(millis());
DEBUG.print(']');
DEBUG.print(s);
}
endif
/*************************************************************************
* OBD-II I2C Adapter
*************************************************************************/
void COBDI2C::begin(byte addr)
{
m_addr = addr;
Wire.begin();
}
bool COBDI2C::init()
{
m_state = OBD_CONNECTING;
sendCommand(CMD_QUERY_STATUS);
char recvbuf[MAX_PAYLOAD_SIZE];
for (byte n = 0; n < 3; n++) {
memset(recvbuf, 0, sizeof(recvbuf));
receive(recvbuf);
if (!memcmp(recvbuf, "OBD ", 4))
break;
}
if (recvbuf[4] == 'Y') {
memcpy(pidmap, recvbuf + 16, sizeof(pidmap));
m_state = OBD_CONNECTED;
return true;
} else {
m_state = OBD_DISCONNECTED;
return false;
}
}
bool COBDI2C::read(byte pid, int& result, bool passive)
{
uint32_t t = millis();
sendQuery(pid);
dataIdleLoop();
return getResponseParsed(pid, result);
}
void COBDI2C::write(char* s)
{
COMMAND_BLOCK cmdblock = {millis(), CMD_SEND_COMMAND};
Wire.beginTransmission(m_addr);
Wire.write((byte*)&cmdblock, sizeof(cmdblock));
Wire.write(s);
Wire.endTransmission();
}
bool COBDI2C::sendCommand(byte cmd, uint8_t data, byte* payload, byte payloadBytes)
{
COMMAND_BLOCK cmdblock = {millis(), cmd, data};
Wire.beginTransmission(m_addr);
bool success = Wire.write((byte*)&cmdblock, sizeof(COMMAND_BLOCK)) == sizeof(COMMAND_BLOCK);
if (payload) Wire.write(payload, payloadBytes);
Wire.endTransmission();
return success;
}
byte COBDI2C::receive(char* buffer)
{
uint32_t start = millis();
byte offset = 0;
do {
Wire.requestFrom((byte)m_addr, (byte)MAX_PAYLOAD_SIZE, (byte)1);
bool hasEnd = false;
for (byte i = 0; i < MAX_PAYLOAD_SIZE; i++) {
if ((buffer[offset + i] = Wire.read()) == 0)
hasEnd = true;
}
if (buffer[0] == 0) {
// data not ready
dataIdleLoop();
continue;
}
offset += MAX_PAYLOAD_SIZE;
if (!hasEnd) {
continue;
}
return offset;
} while(millis() - start < OBD_TIMEOUT_LONG);
return 0;
}
bool COBDI2C::btInit(uint16_t baudrate)
{
return sendCommand(CMD_UART_BEGIN, baudrate / 1200);
}
bool COBDI2C::btSend(byte* data, byte length)
{
return sendCommand(CMD_UART_SEND, 0, data, length);
}
bool COBDI2C::btReceive(byte* buffer, byte bufsize)
{
if (!sendCommand(CMD_UART_RECV, bufsize)) return false;
memset(buffer, 0, MAX_PAYLOAD_SIZE);
Wire.requestFrom((byte)m_addr, (byte)MAX_PAYLOAD_SIZE, (byte)1);
Wire.readBytes((char*)buffer, MAX_PAYLOAD_SIZE);
return true;
}