/************************************************************************* * Arduino Library for Freematics ONE+ * Distributed under BSD license * Visit https://freematics.com for more information * (C)2012-2019 Developed by Stanley Huang *************************************************************************/ #include #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); }