freematics-traccar-encrypted/esp32/libraries/FreematicsONE/FreematicsONE.cpp

738 lines
16 KiB
C++
Raw Normal View History

2024-06-30 18:59:23 -06:00
/*************************************************************************
* Arduino Library for Freematics ONE
* Distributed under BSD license
* Visit http://freematics.com/products/freematics-one for more information
* (C)2012-2018 Stanley Huang <stanley@freematics.com.au>
*************************************************************************/
#include <Arduino.h>
#include <SPI.h>
#include "FreematicsONE.h"
//#define XBEE_DEBUG
//#define DEBUG Serial
static const uint8_t targets[][4] = {
{0x24, 0x4f, 0x42, 0x44},
{0x24, 0x47, 0x50, 0x53},
{0x24, 0x47, 0x53, 0x4D}
};
static SPISettings settings = SPISettings(250000, MSBFIRST, SPI_MODE0);
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 >= '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);
}
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;
}
byte COBDSPI::readDTC(uint16_t codes[], byte maxCodes)
{
/*
Response example:
0: 43 04 01 08 01 09
1: 01 11 01 15 00 00 00
*/
byte codesRead = 0;
for (byte n = 0; n < 6; n++) {
char buffer[128];
sprintf_P(buffer, n == 0 ? PSTR("03\r") : PSTR("03%02X\r"), n);
write(buffer);
if (receive(buffer, sizeof(buffer)) > 0) {
if (!strstr_P(buffer, PSTR("NO DATA"))) {
char *p = strstr_P(buffer, PSTR("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 COBDSPI::clearDTC()
{
char buffer[32];
write("04\r");
receive(buffer, sizeof(buffer));
}
void COBDSPI::lowPowerMode()
{
char buf[16];
sendCommand("ATLP\r", buf, sizeof(buf));
}
bool COBDSPI::testPID(byte pid)
{
char buffer[32];
sprintf_P(buffer, PSTR("%02X%02X\r"), dataMode, pid);
write(buffer);
idleTasks();
if (receive(buffer, sizeof(buffer)) <= 0 || checkErrorMessage(buffer)) {
return false;
}
return true;
}
bool COBDSPI::readPID(byte pid, int& result)
{
char buffer[32];
// send a single query command
sprintf_P(buffer, PSTR("%02X%02X\r"), dataMode, pid);
write(buffer);
// receive and parse the response
idleTasks();
char* data = 0;
if (receive(buffer, sizeof(buffer)) > 0 && !checkErrorMessage(buffer)) {
char *p = buffer;
while ((p = strstr(p, "41 "))) {
p += 3;
byte curpid = hex2uint8(p);
if (curpid == pid) {
errors = 0;
p += 2;
if (*p == ' ') {
data = p + 1;
break;
}
}
}
}
if (!data) {
errors++;
return false;
}
result = normalizeData(pid, data);
return true;
}
int COBDSPI::normalizeData(byte pid, char* data)
{
int result;
switch (pid) {
case PID_RPM:
case PID_EVAP_SYS_VAPOR_PRESSURE:
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:
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:
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;
default:
result = getSmallValue(data);
}
return result;
}
float COBDSPI::getVoltage()
{
char buf[32];
if (sendCommand("ATRV\r", buf, sizeof(buf)) > 0) {
return atof(buf + 4);
}
return 0;
}
bool COBDSPI::getVIN(char* buffer, int bufsize)
{
for (byte n = 0; n < 5; n++) {
if (sendCommand("0902\r", buffer, bufsize)) {
int len = hex2uint16(buffer + sizeof(targets[0]));
char *p = strstr_P(buffer + 4, PSTR("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(200);
}
return false;
}
bool COBDSPI::isValidPID(byte pid)
{
if (pid >= 0x7f)
return true;
pid--;
byte i = pid >> 3;
byte b = 0x80 >> (pid & 0x7);
return pidmap[i] & b;
}
void COBDSPI::reset()
{
char buf[32];
sendCommand("ATR\r", buf, sizeof(buf));
delay(2000);
errors = 0;
}
void COBDSPI::end()
{
char buf[32];
sendCommand("ATPC\r", buf, sizeof(buf));
SPI.end();
}
byte COBDSPI::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;
}
bool COBDSPI::init(OBD_PROTOCOLS protocol)
{
const char *initcmd[] = {"ATZ\r", "ATE0\r", "ATH0\r"};
char buffer[64];
m_state = OBD_DISCONNECTED;
for (byte i = 0; i < sizeof(initcmd) / sizeof(initcmd[0]); i++) {
if (!sendCommand(initcmd[i], buffer, sizeof(buffer), OBD_TIMEOUT_SHORT)) {
continue;
}
}
if (protocol != PROTO_AUTO) {
sprintf_P(buffer, PSTR("ATSP%u\r"), protocol);
sendCommand(buffer, buffer, sizeof(buffer), OBD_TIMEOUT_SHORT);
}
bool success = sendCommand("010D\r", buffer, sizeof(buffer), OBD_TIMEOUT_SHORT) && !checkErrorMessage(buffer);
// load pid map
memset(pidmap, 0xff, sizeof(pidmap));
for (byte i = 0; i < 4; i++) {
byte pid = i * 0x20;
sprintf_P(buffer, PSTR("%02X%02X\r"), dataMode, pid);
write(buffer);
delay(10);
if (!receive(buffer, sizeof(buffer), OBD_TIMEOUT_LONG) || checkErrorMessage(buffer)) break;
for (char *p = buffer; (p = strstr_P(p, PSTR("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 true;
}
#ifdef DEBUG
void COBDSPI::debugOutput(const char *s)
{
DEBUG.print('[');
DEBUG.print(millis());
DEBUG.print(']');
DEBUG.println(s);
}
#endif
byte COBDSPI::begin()
{
m_target = TARGET_OBD;
pinMode(SPI_PIN_READY, INPUT);
pinMode(SPI_PIN_CS, OUTPUT);
digitalWrite(SPI_PIN_CS, HIGH);
SPI.begin();
//SPI.setClockDivider(SPI_CLOCK_DIV64);
return getVersion();
}
byte COBDSPI::getVersion()
{
byte version = 0;
for (byte n = 0; n < 30; n++) {
char buffer[32];
if (sendCommand("ATI\r", buffer, sizeof(buffer), 1000)) {
char *p = strstr_P(buffer, PSTR("OBDUART"));
if (p) {
p += 9;
version = (*p - '0') * 10 + (*(p + 2) - '0');
if (version) break;
}
}
}
return version;
}
int COBDSPI::receive(char* buffer, int bufsize, unsigned int timeout)
{
int n = 0;
bool eos = false;
bool matched = false;
uint32_t t = millis();
do {
while (digitalRead(SPI_PIN_READY) == HIGH) {
if (millis() - t > timeout) return -1;
}
SPI.beginTransaction(settings);
digitalWrite(SPI_PIN_CS, LOW);
while (digitalRead(SPI_PIN_READY) == LOW && millis() - t < timeout) {
char c = SPI.transfer(' ');
if (eos) continue;
if (!matched) {
if (c == '$') {
buffer[0] = c;
n = 1;
matched = true;
} else if (c == 0x9) {
eos = true;
break;
}
continue;
}
if (n > 6 && c == '.' && buffer[n - 1] == '.' && buffer[n - 2] == '.') {
// SEARCHING...
n = 4;
timeout += OBD_TIMEOUT_LONG;
} else if (c == 0x9) {
eos = true;
break;
} else if (c != 0 && c != 0xff && n < bufsize - 1) {
buffer[n++] = c;
}
}
delay(1);
digitalWrite(SPI_PIN_CS, HIGH);
SPI.endTransaction();
} while (!eos && millis() - t < timeout);
#ifdef DEBUG
if (!eos && millis() - t >= timeout) {
// timed out
debugOutput("RECV TIMEOUT");
}
#endif
if (n >= 2 && buffer[n - 1] == '>' && buffer[n - 2] == '\r') n -= 2;
buffer[n] = 0;
#ifdef DEBUG
if (m_target == TARGET_OBD) {
debugOutput(buffer);
}
#endif
// wait for READY pin to restore high level so SPI bus is released
while (digitalRead(SPI_PIN_READY) == LOW);
return n;
}
void COBDSPI::write(const char* s)
{
#ifdef DEBUG
debugOutput(s);
#endif
//delay(1);
SPI.beginTransaction(settings);
digitalWrite(SPI_PIN_CS, LOW);
delay(1);
if (*s != '$') {
for (byte i = 0; i < sizeof(targets[0]); i++) {
SPI.transfer(targets[m_target][i]);
}
}
byte c = 0;
for (; *s ;s++) {
c = *s;
SPI.transfer((byte)c);
}
if (c != '\r') SPI.transfer('\r');
// send terminating byte
SPI.transfer(0x1B);
delay(1);
digitalWrite(SPI_PIN_CS, HIGH);
SPI.endTransaction();
}
byte COBDSPI::sendCommand(const char* cmd, char* buf, int bufsize, unsigned int timeout)
{
uint32_t t = millis();
int n = 0;
for (byte i = 0; i < 30 && millis() - t < timeout; i++) {
write(cmd);
n = receive(buf, bufsize, timeout);
if (n == -1) {
t = millis();
Serial.print('_');
continue;
}
if (n == 0 || (buf[1] != 'O' && !memcmp_P(buf + 5, PSTR("NO DATA"), 7))) {
// data not ready
delay(50);
i = 0;
} else {
break;
}
}
return n;
}
bool COBDSPI::ioConfig(byte pin, IO_PIN_MODE mode)
{
// mode: 0: digital input, 1: digial output
char buf[32];
sprintf_P(buf, PSTR("ATMXCFG %u,%u\r"), pin, mode);
return sendCommand(buf, buf, sizeof(buf), 100) && strstr_P(buf, PSTR("OK"));
}
bool COBDSPI::ioWrite(byte pin, byte level)
{
char buf[32];
sprintf_P(buf, PSTR("ATMXSET %u,%u\r"), pin, level);
return sendCommand(buf, buf, sizeof(buf), 100) && strstr_P(buf, PSTR("OK"));
}
byte COBDSPI::ioRead()
{
char buf[32];
sprintf_P(buf, PSTR("ATMXGET\r"));
sendCommand(buf, buf, sizeof(buf), 50);
char *p = strstr_P(buf, PSTR("MX:"));
if (!p || *(p + 4) != ',') return -1;
return (*(p + 3) - '0') | (*(p + 5) - '0') << 1;
}
bool COBDSPI::gpsInit(unsigned long baudrate)
{
bool success = false;
char buf[64];
if (baudrate) {
if (sendCommand("ATGPSON\r", buf, sizeof(buf))) {
sprintf_P(buf, PSTR("ATBR2%lu\r"), baudrate);
sendCommand(buf, buf, sizeof(buf));
uint32_t t = millis();
delay(100);
do {
if (gpsGetRawData(buf, sizeof(buf)) && strstr_P(buf, PSTR("S$G"))) {
return true;
}
} while (millis() - t < GPS_INIT_TIMEOUT);
}
} else {
success = true;
}
//turn off GPS power
sendCommand("ATGPSOFF\r", buf, sizeof(buf));
return success;
}
bool COBDSPI::gpsGetData(GPS_DATA* gdata)
{
char buf[128];
if (sendCommand("ATGPS\r", buf, sizeof(buf), GPS_READ_TIMEOUT) == 0) {
return false;
}
if (memcmp_P(buf, PSTR("$GPS,"), 5)) {
return false;
}
byte index = 0;
bool valid = true;
char *s = buf + 5;
for (char* p = s; *p && valid; p++) {
char c = *p;
if (c == ',' || c == '>' || c <= 0x0d) {
long value = atol(s);
switch (index) {
case 0:
if (value == 0)
valid = false;
else
gdata->date = (uint32_t)value;
break;
case 1:
gdata->time = (uint32_t)value;
break;
case 2:
gdata->lat = value;
break;
case 3:
gdata->lng = value;
break;
case 4:
gdata->alt = value / 100;
break;
case 5:
gdata->speed = value * 100000 / 1852;
break;
case 6:
gdata->heading = value;
break;
case 7:
gdata->sat = value;
break;
default:
valid = false;
}
index++;
if (c != ',') break;
s = p + 1;
}
}
return valid && index > 7;
}
int COBDSPI::gpsGetRawData(char* buf, int bufsize)
{
int n = sendCommand("ATGRR\r", buf, bufsize, GPS_READ_TIMEOUT);
if (n > 2) {
n -= 2;
buf[n] = 0;
}
return n;
}
void COBDSPI::gpsSendCommand(const char* cmd)
{
m_target = TARGET_GPS;
write(cmd);
m_target = TARGET_OBD;
}
bool COBDSPI::xbBegin(unsigned long baudrate)
{
char buf[16];
sprintf_P(buf, PSTR("ATBR1%lu\r"), baudrate);
if (sendCommand(buf, buf, sizeof(buf))) {
xbPurge();
return true;
} else {
return false;
}
}
void COBDSPI::xbWrite(const char* cmd)
{
#ifdef XBEE_DEBUG
Serial.print("[SEND]");
Serial.println(cmd);
Serial.println("[/SEND]");
#endif
m_target = TARGET_BEE;
write(cmd);
m_target = TARGET_OBD;
}
int COBDSPI::xbRead(char* buffer, int bufsize)
{
write("ATGRD\r");
delay(10);
int bytes = receive(buffer, bufsize, 500);
if (bytes < 4 || memcmp(buffer, targets[TARGET_BEE], 4)) {
bytes = -1;
} else if (bytes >= 11 && !memcmp_P(buffer + 4, PSTR("NO DATA"), 7)) {
buffer[0] = 0;
bytes = 0;
}
return bytes;
}
int COBDSPI::xbReceive(char* buffer, int bufsize, unsigned int timeout, const char** expected, byte expectedCount)
{
int bytesRecv = 0;
uint32_t t = millis();
buffer[0] = 0;
do {
while (bytesRecv >= bufsize / 2) {
bytesRecv -= dumpLine(buffer, bytesRecv);
}
int n = xbRead(buffer + bytesRecv, bufsize - bytesRecv);
if (n > 0) {
buffer[bytesRecv + n] = 0;
// skip 4-byte header
char *p = buffer + bytesRecv + 4;
n -= 4;
if (bytesRecv == 0 && *p == '\n') {
// strip first \n
p++;
n--;
}
memmove(buffer + bytesRecv, p, n);
bytesRecv += n;
buffer[bytesRecv] = 0;
#ifdef XBEE_DEBUG
if (n > 0) {
Serial.print("[RECV]");
Serial.print(buffer + bytesRecv - n);
Serial.println("[/RECV]");
}
#endif
if (expectedCount == 0 && bytesRecv > 0) {
return bytesRecv;
}
for (byte i = 0; i < expectedCount; i++) {
// match expected string(s)
if (expected[i] && strstr(buffer, expected[i])) return i + 1;
}
delay(50);
} if (n < 0) {
break;
} else {
if (millis() - t + 200 < timeout)
delay(200);
else
break;
}
} while (millis() - t < timeout);
return 0;
}
void COBDSPI::xbPurge()
{
char buf[32];
sendCommand("ATCLRGSM\r", buf, sizeof(buf));
}
void COBDSPI::xbTogglePower()
{
char buf[32];
sendCommand("ATGSMPWR\r", buf, sizeof(buf));
}