freematics-traccar-encrypted/esp32/libraries/FreematicsPlus/FreematicsOBD.cpp

563 lines
12 KiB
C++
Raw Permalink Normal View History

2024-06-30 18:59:23 -06:00
/*************************************************************************
* Arduino Library for Freematics ONE+
* Distributed under BSD license
* Visit https://freematics.com for more information
* (C)2012-2019 Developed by Stanley Huang <stanley@freematics.com.au>
*************************************************************************/
#include <Arduino.h>
#include "FreematicsBase.h"
#include "FreematicsOBD.h"
int dumpLine(char* buffer, int len)
{
int bytesToDump = len >> 1;
for (int i = 0; i < len; i++) {
// find out first line end and discard the first line
if (buffer[i] == '\r' || buffer[i] == '\n') {
// go through all following \r or \n if any
while (++i < len && (buffer[i] == '\r' || buffer[i] == '\n'));
bytesToDump = i;
break;
}
}
memmove(buffer, buffer + bytesToDump, len - bytesToDump);
return bytesToDump;
}
uint16_t hex2uint16(const char *p)
{
char c = *p;
uint16_t i = 0;
for (uint8_t n = 0; c && n < 4; c = *(++p)) {
if (c >= 'A' && c <= 'F') {
c -= 7;
} else if (c>='a' && c<='f') {
c -= 39;
} else if (c == ' ' && n == 2) {
continue;
} else if (c < '0' || c > '9') {
break;
}
i = (i << 4) | (c & 0xF);
n++;
}
return i;
}
byte hex2uint8(const char *p)
{
byte c1 = *p;
byte c2 = *(p + 1);
if (c1 >= 'A' && c1 <= 'F')
c1 -= 7;
else if (c1 >= 'a' && c1 <= 'f')
c1 -= 39;
else if (c1 < '0' || c1 > '9')
return 0;
if (c2 == 0)
return (c1 & 0xf);
else if (c2 >= 'A' && c2 <= 'F')
c2 -= 7;
else if (c2 >= 'a' && c2 <= 'f')
c2 -= 39;
else if (c2 < '0' || c2 > '9')
return 0;
return c1 << 4 | (c2 & 0xf);
}
/*************************************************************************
* OBD-II UART Bridge
*************************************************************************/
bool COBD::readPID(byte pid, int& result)
{
char buffer[64];
char* data = 0;
sprintf(buffer, "%02X%02X\r", dataMode, pid);
link->send(buffer);
idleTasks();
int ret = link->receive(buffer, sizeof(buffer), OBD_TIMEOUT_SHORT);
if (ret > 0 && !checkErrorMessage(buffer)) {
char *p = buffer;
while ((p = strstr(p, "41 "))) {
p += 3;
byte curpid = hex2uint8(p);
if (curpid == pid) {
errors = 0;
while (*p && *p != ' ') p++;
while (*p == ' ') p++;
if (*p) {
data = p;
break;
}
}
}
}
if (!data) {
errors++;
return false;
}
result = normalizeData(pid, data);
return true;
}
byte COBD::readPID(const byte pid[], byte count, int result[])
{
byte results = 0;
for (byte n = 0; n < count; n++) {
if (readPID(pid[n], result[n])) {
results++;
}
}
return results;
}
int COBD::readDTC(uint16_t codes[], byte maxCodes)
{
/*
Response example:
0: 43 04 01 08 01 09
1: 01 11 01 15 00 00 00
*/
int codesRead = 0;
if (!link) return 0;
for (int n = 0; n < 6; n++) {
char buffer[128];
sprintf(buffer, n == 0 ? "03\r" : "03%02X\r", n);
link->send(buffer);
if (link->receive(buffer, sizeof(buffer), OBD_TIMEOUT_LONG) > 0) {
if (!strstr(buffer, "NO DATA")) {
char *p = strstr(buffer, "43");
if (p) {
while (codesRead < maxCodes && *p) {
p += 6;
if (*p == '\r') {
p = strchr(p, ':');
if (!p) break;
p += 2;
}
uint16_t code = hex2uint16(p);
if (code == 0) break;
codes[codesRead++] = code;
}
}
break;
}
}
}
return codesRead;
}
void COBD::clearDTC()
{
char buffer[32];
link->send("04\r");
link->receive(buffer, sizeof(buffer), OBD_TIMEOUT_LONG);
}
int COBD::normalizeData(byte pid, char* data)
{
int result;
switch (pid) {
case PID_RPM:
case PID_EVAP_SYS_VAPOR_PRESSURE: // kPa
result = getLargeValue(data) >> 2;
break;
case PID_FUEL_PRESSURE: // kPa
result = getSmallValue(data) * 3;
break;
case PID_COOLANT_TEMP:
case PID_INTAKE_TEMP:
case PID_AMBIENT_TEMP:
case PID_ENGINE_OIL_TEMP:
result = getTemperatureValue(data);
break;
case PID_THROTTLE:
case PID_COMMANDED_EGR:
case PID_COMMANDED_EVAPORATIVE_PURGE:
case PID_FUEL_LEVEL:
case PID_RELATIVE_THROTTLE_POS:
case PID_ABSOLUTE_THROTTLE_POS_B:
case PID_ABSOLUTE_THROTTLE_POS_C:
case PID_ACC_PEDAL_POS_D:
case PID_ACC_PEDAL_POS_E:
case PID_ACC_PEDAL_POS_F:
case PID_COMMANDED_THROTTLE_ACTUATOR:
case PID_ENGINE_LOAD:
case PID_ABSOLUTE_ENGINE_LOAD:
case PID_ETHANOL_FUEL:
case PID_HYBRID_BATTERY_PERCENTAGE:
result = getPercentageValue(data);
break;
case PID_MAF_FLOW: // grams/sec
result = getLargeValue(data) / 100;
break;
case PID_TIMING_ADVANCE:
result = (int)(getSmallValue(data) / 2) - 64;
break;
case PID_DISTANCE: // km
case PID_DISTANCE_WITH_MIL: // km
case PID_TIME_WITH_MIL: // minute
case PID_TIME_SINCE_CODES_CLEARED: // minute
case PID_RUNTIME: // second
case PID_FUEL_RAIL_PRESSURE: // kPa
case PID_ENGINE_REF_TORQUE: // Nm
result = getLargeValue(data);
break;
case PID_CONTROL_MODULE_VOLTAGE: // V
result = getLargeValue(data) / 1000;
break;
case PID_ENGINE_FUEL_RATE: // L/h
result = getLargeValue(data) / 20;
break;
case PID_ENGINE_TORQUE_DEMANDED: // %
case PID_ENGINE_TORQUE_PERCENTAGE: // %
result = (int)getSmallValue(data) - 125;
break;
case PID_SHORT_TERM_FUEL_TRIM_1:
case PID_LONG_TERM_FUEL_TRIM_1:
case PID_SHORT_TERM_FUEL_TRIM_2:
case PID_LONG_TERM_FUEL_TRIM_2:
case PID_EGR_ERROR:
result = ((int)getSmallValue(data) - 128) * 100 / 128;
break;
case PID_FUEL_INJECTION_TIMING:
result = ((int32_t)getLargeValue(data) - 26880) / 128;
break;
case PID_CATALYST_TEMP_B1S1:
case PID_CATALYST_TEMP_B2S1:
case PID_CATALYST_TEMP_B1S2:
case PID_CATALYST_TEMP_B2S2:
result = getLargeValue(data) / 10 - 40;
break;
case PID_AIR_FUEL_EQUIV_RATIO: // 0~200
result = (long)getLargeValue(data) * 200 / 65536;
break;
case PID_ODOMETER:
if (strlen(data) < 11)
result = -1;
else
result = (uint32_t)hex2uint8(data) << 24 | (uint32_t)hex2uint8(data + 3) << 16 | (uint32_t)hex2uint8(data + 6) << 8 | hex2uint8(data + 9);
break;
default:
result = getSmallValue(data);
}
return result;
}
char* COBD::getResponse(byte& pid, char* buffer, byte bufsize)
{
if (!link) return 0;
while (link->receive(buffer, bufsize, OBD_TIMEOUT_SHORT) > 0) {
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;
}
void COBD::enterLowPowerMode()
{
char buf[32];
if (link) {
reset();
delay(1000);
link->sendCommand("ATLP\r", buf, sizeof(buf), 1000);
}
}
void COBD::leaveLowPowerMode()
{
// send any command to wake up
char buf[32];
if (!link) return;
for (byte n = 0; n < 30 && !link->sendCommand("ATI\r", buf, sizeof(buf), 1000); n++);
}
char* COBD::getResultValue(char* buf)
{
char* p = buf;
for (;;) {
if (isdigit(*p) || *p == '-') {
return p;
}
p = strchr(p, '\r');
if (!p) break;
if (*(++p) == '\n') p++;
}
return 0;
}
float COBD::getVoltage()
{
char buf[32];
if (link && link->sendCommand("ATRV\r", buf, sizeof(buf), 500) > 0) {
char* p = getResultValue(buf);
if (p) return (float)atof(p);
}
return 0;
}
bool COBD::getVIN(char* buffer, byte bufsize)
{
for (byte n = 0; n < 2; n++) {
if (link && link->sendCommand("0902\r", buffer, bufsize, OBD_TIMEOUT_LONG)) {
int len = hex2uint16(buffer);
char *p = strstr(buffer + 4, "0: 49 02 01");
if (p) {
char *q = buffer;
p += 11; // skip the header
do {
while (*(++p) == ' ');
for (;;) {
*(q++) = hex2uint8(p);
while (*p && *p != ' ') p++;
while (*p == ' ') p++;
if (!*p || *p == '\r') break;
}
p = strchr(p, ':');
} while(p);
*q = 0;
if (q - buffer == len - 3) {
return true;
}
}
}
delay(100);
}
return false;
}
bool COBD::isValidPID(byte pid)
{
pid--;
byte i = pid >> 3;
byte b = 0x80 >> (pid & 0x7);
return (pidmap[i] & b) != 0;
}
bool COBD::init(OBD_PROTOCOLS protocol, bool quick)
{
const char *initcmd[] = {"ATE0\r", "ATH0\r"};
char buffer[64];
bool success = false;
if (!link) {
return false;
}
m_state = OBD_DISCONNECTED;
for (byte n = 0; n < 3; n++) {
if (link->sendCommand("ATZ\r", buffer, sizeof(buffer), OBD_TIMEOUT_SHORT)) {
success = true;
break;
}
}
if (!success) return false;
for (byte i = 0; i < sizeof(initcmd) / sizeof(initcmd[0]); i++) {
link->sendCommand(initcmd[i], buffer, sizeof(buffer), OBD_TIMEOUT_SHORT);
}
if (protocol != PROTO_AUTO) {
sprintf(buffer, "ATSP %X\r", protocol);
if (!link->sendCommand(buffer, buffer, sizeof(buffer), OBD_TIMEOUT_SHORT) || !strstr(buffer, "OK")) {
return false;
}
}
if (protocol == PROTO_J1939) {
m_state = OBD_CONNECTED;
errors = 0;
return true;
}
success = false;
if (quick) {
int value;
if (!readPID(PID_SPEED, value)) return false;
} else {
for (byte n = 0; n < 2; n++) {
int value;
if (readPID(PID_SPEED, value)) {
success = true;
break;
}
}
if (!success) {
return false;
}
}
// load pid map
memset(pidmap, 0xff, sizeof(pidmap));
for (byte i = 0; i < 8; i++) {
byte pid = i * 0x20;
sprintf(buffer, "%02X%02X\r", dataMode, pid);
link->send(buffer);
if (!link->receive(buffer, sizeof(buffer), OBD_TIMEOUT_LONG) || checkErrorMessage(buffer)) {
break;
}
for (char *p = buffer; (p = strstr(p, "41 ")); ) {
p += 3;
if (hex2uint8(p) == pid) {
p += 2;
for (byte n = 0; n < 4 && *(p + n * 3) == ' '; n++) {
pidmap[i * 4 + n] = hex2uint8(p + n * 3 + 1);
}
success = true;
}
}
}
if (success) {
m_state = OBD_CONNECTED;
errors = 0;
}
return success;
}
void COBD::reset()
{
char buf[32];
if (link) link->sendCommand("ATR\r", buf, sizeof(buf), OBD_TIMEOUT_SHORT);
}
void COBD::uninit()
{
char buf[32];
if (link) link->sendCommand("ATPC\r", buf, sizeof(buf), OBD_TIMEOUT_SHORT);
}
byte COBD::checkErrorMessage(const char* buffer)
{
const char *errmsg[] = {"UNABLE", "ERROR", "TIMEOUT", "NO DATA"};
for (byte i = 0; i < sizeof(errmsg) / sizeof(errmsg[0]); i++) {
if (strstr(buffer, errmsg[i])) return i + 1;
}
return 0;
}
uint8_t COBD::getPercentageValue(char* data)
{
return (uint16_t)hex2uint8(data) * 100 / 255;
}
uint16_t COBD::getLargeValue(char* data)
{
return hex2uint16(data);
}
uint8_t COBD::getSmallValue(char* data)
{
return hex2uint8(data);
}
int16_t COBD::getTemperatureValue(char* data)
{
return (int)hex2uint8(data) - 40;
}
void COBD::setHeaderID(uint32_t num)
{
if (link) {
char buf[32];
sprintf(buf, "ATSH %X\r", num & 0xffffff);
link->sendCommand(buf, buf, sizeof(buf), 1000);
sprintf(buf, "ATCP %X\r", num & 0x1f);
link->sendCommand(buf, buf, sizeof(buf), 1000);
}
}
void COBD::sniff(bool enabled)
{
if (link) {
char buf[32];
link->sendCommand(enabled ? "ATM1\r" : "ATM0\r", buf, sizeof(buf), 1000);
}
}
void COBD::setHeaderFilter(uint32_t num)
{
if (link) {
char buf[32];
sprintf(buf, "ATCF %X\r", num);
link->sendCommand(buf, buf, sizeof(buf), 1000);
}
}
void COBD::setHeaderMask(uint32_t bitmask)
{
if (link) {
char buf[32];
sprintf(buf, "ATCM %X\r", bitmask);
link->sendCommand(buf, buf, sizeof(buf), 1000);
}
}
int COBD::receiveData(byte* buf, int len)
{
if (!link) return 0;
int n = 0;
for (n = 0; n < len; ) {
int c = link->read();
if (c == -1 || c == '\r') break;
buf[n++] = c;
}
if (n == 0) return 0;
int bytes = 0;
len = n;
if (buf[0] == '$') {
for (n = 1; n < len && buf[n] != ','; n++);
for (; n < len && buf[n] == ','; bytes++) {
byte d = hex2uint8((const char*)buf + n + 1);
n += 3;
if (buf[n] != ',' && buf[n] != '\r') {
if (d != hex2uint8((const char*)buf + n)) break;
n += 2;
}
buf[bytes] = d;
}
} else {
for (n = 0; n < len; bytes++) {
buf[bytes] = hex2uint8((const char*)buf + n);
n += 2;
if (buf[n++] != ' ') break;
}
}
return bytes;
}
void COBD::setCANID(uint16_t id)
{
if (link) {
char buf[32];
sprintf(buf, "ATSH %X\r", id);
link->sendCommand(buf, buf, sizeof(buf), 1000);
}
}
int COBD::sendCANMessage(byte msg[], int len, char* buf, int bufsize)
{
if (!link) return 0;
char cmd[258];
if (len * 2 >= sizeof(cmd) - 1) len = sizeof(cmd) / 2 - 2;
for (int n = 0; n < len; n++) {
sprintf(cmd + n * 2, "%02X", msg[n]);
}
cmd[len * 2] = '\r';
cmd[len * 2 + 1] = 0;
return link->sendCommand(cmd, buf, bufsize, 100);
}