diff --git a/.gitignore b/.gitignore index 259148f..8c01f2e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ + +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch + # Prerequisites *.d @@ -30,3 +37,10 @@ *.exe *.out *.app +include/README +lib/README +test/README +.vscode/extensions.json +platformio.ini +.gitignore +.vscode/settings.json diff --git a/README.md b/README.md index 4a1de6e..4c00cea 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ELMduino [![GitHub version](https://badge.fury.io/gh/PowerBroker2%2FELMduino.svg)](https://badge.fury.io/gh/PowerBroker2%2FELMduino) [![arduino-library-badge](https://www.ardu-badge.com/badge/ELMDuino.svg?)](https://www.ardu-badge.com/ELMDuino)

-This is a simple yet powerful library to effortlessly interface your Arduino with an ELM327 OBD-II scanner. With this library, you can query any and all OBD-II supported PIDs to collect a wide variety of car data (i.e. speed, rpm, engine temp, etc). Also, you can use ELMduino to view and clear your car's trouble codes - no need ot go to AutoZone anymore! +This is a simple yet powerful library to effortlessly interface your Arduino with an ELM327 OBD-II scanner. With this library, you can query any and all OBD-II supported PIDs to collect a wide variety of car data (i.e. speed, rpm, engine temp, etc). Also, you can use ELMduino to view and clear your car's trouble codes - no need to go to AutoZone anymore! # Example Project: @@ -9,383 +9,113 @@ This is a simple yet powerful library to effortlessly interface your Arduino wit # Install: Install ELMduino using the Arduino IDE's Libraries Manager (search "ELMduino.h") +# Available Features: +See the contents of ELMduino.h for lists of supported OBD PID processing functions, OBD protocols, standard PIDs, and AT commands. The associated functions are documented in "doc strings" in ELMduino.cpp. + # Note: If you're having difficulty in connecting/keeping connection to your ELM327, try using 38400 baud instead of 115200. If you still have trouble, try all other possible bauds. Lastly, if using BluetoothSerial on an ESP32, try using the ELM327's MAC address instead of the device name "OBDII" and [remove paired devices using this sketch](https://github.com/espressif/arduino-esp32/blob/master/libraries/BluetoothSerial/examples/bt_remove_paired_devices/bt_remove_paired_devices.ino). +# Concept of Execution +The library is non-blocking. This means when you query a PID e.g. `myELM327.rpm()`, the code does not wait around for the response, which would block your other code in the main loop from executing. With ELMDuino, your main loop can continue do other tasks. To make this work, you need to repeatedly call the PID query function and check the non-blocking receive state (`myELM327.nb_rx_state`) until it is equal to `ELM_SUCCESS`. If the status is not `ELM_SUCCESS`, the library could still be waiting for a response to be received. This is indicated by `myELM327.nb_rx_state` being equal to `ELM_GETTING_MSG`. If the status is not equal to either of these values (ELM_SUCCESS or ELM_GETTING_MSG), it indicates an error has occurred. You can call `myELM327.printError()` to check what the problem was. See the simple example below which queries the engine speed in RPM. + +Just to be clear, do not try to query more than one PID at a time. You must wait for the current PID query to complete before starting the next one. + # Example Code: ```C++ -#include #include "ELMduino.h" -SoftwareSerial mySerial(2, 3); // RX, TX -#define ELM_PORT mySerial + + +#define ELM_PORT Serial1 + + + + +const bool DEBUG = true; +const int TIMEOUT = 2000; +const bool HALT_ON_FAIL = false; + + ELM327 myELM327; -uint32_t rpm = 0; + + +typedef enum { ENG_RPM, + SPEED } obd_pid_states; +obd_pid_states obd_state = ENG_RPM; + +float rpm = 0; +float mph = 0; + + void setup() { - pinMode(LED_BUILTIN, OUTPUT); - digitalWrite(LED_BUILTIN, HIGH); - Serial.begin(115200); ELM_PORT.begin(115200); Serial.println("Attempting to connect to ELM327..."); - if (!myELM327.begin(ELM_PORT)) + if (!myELM327.begin(ELM_PORT, DEBUG, TIMEOUT)) { Serial.println("Couldn't connect to OBD scanner"); - while (1); + + if (HALT_ON_FAIL) + while (1); } Serial.println("Connected to ELM327"); } + + void loop() { - float tempRPM = myELM327.rpm(); - - if (myELM327.status == ELM_SUCCESS) - { - rpm = (uint32_t)tempRPM; - Serial.print("RPM: "); Serial.println(rpm); - } - else + switch (obd_state) { - Serial.print(F("\tERROR: ")); - Serial.println(myELM327.status); - delay(100); + case ENG_RPM: + { + rpm = myELM327.rpm(); + + if (myELM327.nb_rx_state == ELM_SUCCESS) + { + Serial.print("rpm: "); + Serial.println(rpm); + obd_state = SPEED; + } + else if (myELM327.nb_rx_state != ELM_GETTING_MSG) + { + myELM327.printError(); + obd_state = SPEED; + } + + break; + } + + case SPEED: + { + mph = myELM327.mph(); + + if (myELM327.nb_rx_state == ELM_SUCCESS) + { + Serial.print("mph: "); + Serial.println(mph); + obd_state = ENG_RPM; + } + else if (myELM327.nb_rx_state != ELM_GETTING_MSG) + { + myELM327.printError(); + obd_state = ENG_RPM; + } + + break; + } } } ``` - -# List of Supported OBD PID Processing Functions: -```C++ -uint32_t supportedPIDs_1_20(); - -uint32_t monitorStatus(); -uint16_t freezeDTC(); -uint16_t fuelSystemStatus(); -float engineLoad(); -float engineCoolantTemp(); -float shortTermFuelTrimBank_1(); -float longTermFuelTrimBank_1(); -float shortTermFuelTrimBank_2(); -float longTermFuelTrimBank_2(); -float fuelPressure(); -uint8_t manifoldPressure(); -float rpm(); -int32_t kph(); -float mph(); -float timingAdvance(); -float intakeAirTemp(); -float mafRate(); -float throttle(); -uint8_t commandedSecAirStatus(); -uint8_t oxygenSensorsPresent_2banks(); -uint8_t obdStandards(); -uint8_t oxygenSensorsPresent_4banks(); -bool auxInputStatus(); -uint16_t runTime(); - - -uint32_t supportedPIDs_21_40(); - -uint16_t distTravelWithMIL(); -float fuelRailPressure(); -float fuelRailGuagePressure(); -float commandedEGR(); -float egrError(); -float commandedEvapPurge(); -float fuelLevel(); -uint8_t warmUpsSinceCodesCleared(); -uint16_t distSinceCodesCleared(); -float evapSysVapPressure(); -uint8_t absBaroPressure(); -float catTempB1S1(); -float catTempB2S1(); -float catTempB1S2(); -float catTempB2S2(); - - -uint32_t supportedPIDs_41_60(); - -uint32_t monitorDriveCycleStatus(); -float ctrlModVoltage(); -float absLoad(); -float commandedAirFuelRatio(); -float relativeThrottle(); -float ambientAirTemp(); -float absThrottlePosB(); -float absThrottlePosC(); -float absThrottlePosD(); -float absThrottlePosE(); -float absThrottlePosF(); -float commandedThrottleActuator(); -uint16_t timeRunWithMIL(); -uint16_t timeSinceCodesCleared(); -float maxMafRate(); -uint8_t fuelType(); -float ethonolPercent(); -float absEvapSysVapPressure(); -float evapSysVapPressure2(); -float absFuelRailPressure(); -float relativePedalPos(); -float hybridBatLife(); -float oilTemp(); -float fuelInjectTiming(); -float fuelRate(); -uint8_t emissionRqmts(); - - -uint32_t supportedPIDs_61_80(); - -float demandedTorque(); -float torque(); -uint16_t referenceTorque(); -uint16_t auxSupported(); -``` - -# List of OBD Protocols: -```C++ -const char AUTOMATIC = '0'; -const char SAE_J1850_PWM_41_KBAUD = '1'; -const char SAE_J1850_PWM_10_KBAUD = '2'; -const char ISO_9141_5_BAUD_INIT = '3'; -const char ISO_14230_5_BAUD_INIT = '4'; -const char ISO_14230_FAST_INIT = '5'; -const char ISO_15765_11_BIT_500_KBAUD = '6'; -const char ISO_15765_29_BIT_500_KBAUD = '7'; -const char ISO_15765_11_BIT_250_KBAUD = '8'; -const char ISO_15765_29_BIT_250_KBAUD = '9'; -const char SAE_J1939_29_BIT_250_KBAUD = 'A'; -const char USER_1_CAN = 'B'; -const char USER_2_CAN = 'C'; -``` - -# List of standard PIDs: -```C++ -const uint8_t SUPPORTED_PIDS_1_20 = 0; // 0x00 - bit encoded -const uint8_t MONITOR_STATUS_SINCE_DTC_CLEARED = 1; // 0x01 - bit encoded -const uint8_t FREEZE_DTC = 2; // 0x02 - -const uint8_t FUEL_SYSTEM_STATUS = 3; // 0x03 - bit encoded -const uint8_t ENGINE_LOAD = 4; // 0x04 - % -const uint8_t ENGINE_COOLANT_TEMP = 5; // 0x05 - °C -const uint8_t SHORT_TERM_FUEL_TRIM_BANK_1 = 6; // 0x06 - % -const uint8_t LONG_TERM_FUEL_TRIM_BANK_1 = 7; // 0x07 - % -const uint8_t SHORT_TERM_FUEL_TRIM_BANK_2 = 8; // 0x08 - % -const uint8_t LONG_TERM_FUEL_TRIM_BANK_2 = 9; // 0x09 - % -const uint8_t FUEL_PRESSURE = 10; // 0x0A - kPa -const uint8_t INTAKE_MANIFOLD_ABS_PRESSURE = 11; // 0x0B - kPa -const uint8_t ENGINE_RPM = 12; // 0x0C - rpm -const uint8_t VEHICLE_SPEED = 13; // 0x0D - km/h -const uint8_t TIMING_ADVANCE = 14; // 0x0E - ° before TDC -const uint8_t INTAKE_AIR_TEMP = 15; // 0x0F - °C -const uint8_t MAF_FLOW_RATE = 16; // 0x10 - g/s -const uint8_t THROTTLE_POSITION = 17; // 0x11 - % -const uint8_t COMMANDED_SECONDARY_AIR_STATUS = 18; // 0x12 - bit encoded -const uint8_t OXYGEN_SENSORS_PRESENT_2_BANKS = 19; // 0x13 - bit encoded -const uint8_t OXYGEN_SENSOR_1_A = 20; // 0x14 - V % -const uint8_t OXYGEN_SENSOR_2_A = 21; // 0x15 - V % -const uint8_t OXYGEN_SENSOR_3_A = 22; // 0x16 - V % -const uint8_t OXYGEN_SENSOR_4_A = 23; // 0x17 - V % -const uint8_t OXYGEN_SENSOR_5_A = 24; // 0x18 - V % -const uint8_t OXYGEN_SENSOR_6_A = 25; // 0x19 - V % -const uint8_t OXYGEN_SENSOR_7_A = 26; // 0x1A - V % -const uint8_t OXYGEN_SENSOR_8_A = 27; // 0x1B - V % -const uint8_t OBD_STANDARDS = 28; // 0x1C - bit encoded -const uint8_t OXYGEN_SENSORS_PRESENT_4_BANKS = 29; // 0x1D - bit encoded -const uint8_t AUX_INPUT_STATUS = 30; // 0x1E - bit encoded -const uint8_t RUN_TIME_SINCE_ENGINE_START = 31; // 0x1F - sec - -const uint8_t SUPPORTED_PIDS_21_40 = 32; // 0x20 - bit encoded -const uint8_t DISTANCE_TRAVELED_WITH_MIL_ON = 33; // 0x21 - km -const uint8_t FUEL_RAIL_PRESSURE = 34; // 0x22 - kPa -const uint8_t FUEL_RAIL_GUAGE_PRESSURE = 35; // 0x23 - kPa -const uint8_t OXYGEN_SENSOR_1_B = 36; // 0x24 - ratio V -const uint8_t OXYGEN_SENSOR_2_B = 37; // 0x25 - ratio V -const uint8_t OXYGEN_SENSOR_3_B = 38; // 0x26 - ratio V -const uint8_t OXYGEN_SENSOR_4_B = 39; // 0x27 - ratio V -const uint8_t OXYGEN_SENSOR_5_B = 40; // 0x28 - ratio V -const uint8_t OXYGEN_SENSOR_6_B = 41; // 0x29 - ratio V -const uint8_t OXYGEN_SENSOR_7_B = 42; // 0x2A - ratio V -const uint8_t OXYGEN_SENSOR_8_B = 43; // 0x2B - ratio V -const uint8_t COMMANDED_EGR = 44; // 0x2C - % -const uint8_t EGR_ERROR = 45; // 0x2D - % -const uint8_t COMMANDED_EVAPORATIVE_PURGE = 46; // 0x2E - % -const uint8_t FUEL_TANK_LEVEL_INPUT = 47; // 0x2F - % -const uint8_t WARM_UPS_SINCE_CODES_CLEARED = 48; // 0x30 - count -const uint8_t DIST_TRAV_SINCE_CODES_CLEARED = 49; // 0x31 - km -const uint8_t EVAP_SYSTEM_VAPOR_PRESSURE = 50; // 0x32 - Pa -const uint8_t ABS_BAROMETRIC_PRESSURE = 51; // 0x33 - kPa -const uint8_t OXYGEN_SENSOR_1_C = 52; // 0x34 - ratio mA -const uint8_t OXYGEN_SENSOR_2_C = 53; // 0x35 - ratio mA -const uint8_t OXYGEN_SENSOR_3_C = 54; // 0x36 - ratio mA -const uint8_t OXYGEN_SENSOR_4_C = 55; // 0x37 - ratio mA -const uint8_t OXYGEN_SENSOR_5_C = 56; // 0x38 - ratio mA -const uint8_t OXYGEN_SENSOR_6_C = 57; // 0x39 - ratio mA -const uint8_t OXYGEN_SENSOR_7_C = 58; // 0x3A - ratio mA -const uint8_t OXYGEN_SENSOR_8_C = 59; // 0x3B - ratio mA -const uint8_t CATALYST_TEMP_BANK_1_SENSOR_1 = 60; // 0x3C - °C -const uint8_t CATALYST_TEMP_BANK_2_SENSOR_1 = 61; // 0x3D - °C -const uint8_t CATALYST_TEMP_BANK_1_SENSOR_2 = 62; // 0x3E - °C -const uint8_t CATALYST_TEMP_BANK_2_SENSOR_2 = 63; // 0x3F - °C - -const uint8_t SUPPORTED_PIDS_41_60 = 64; // 0x40 - bit encoded -const uint8_t MONITOR_STATUS_THIS_DRIVE_CYCLE = 65; // 0x41 - bit encoded -const uint8_t CONTROL_MODULE_VOLTAGE = 66; // 0x42 - V -const uint8_t ABS_LOAD_VALUE = 67; // 0x43 - % -const uint8_t FUEL_AIR_COMMANDED_EQUIV_RATIO = 68; // 0x44 - ratio -const uint8_t RELATIVE_THROTTLE_POSITION = 69; // 0x45 - % -const uint8_t AMBIENT_AIR_TEMP = 70; // 0x46 - °C -const uint8_t ABS_THROTTLE_POSITION_B = 71; // 0x47 - % -const uint8_t ABS_THROTTLE_POSITION_C = 72; // 0x48 - % -const uint8_t ACCELERATOR_PEDAL_POSITION_D = 73; // 0x49 - % -const uint8_t ACCELERATOR_PEDAL_POSITION_E = 74; // 0x4A - % -const uint8_t ACCELERATOR_PEDAL_POSITION_F = 75; // 0x4B - % -const uint8_t COMMANDED_THROTTLE_ACTUATOR = 76; // 0x4C - % -const uint8_t TIME_RUN_WITH_MIL_ON = 77; // 0x4D - min -const uint8_t TIME_SINCE_CODES_CLEARED = 78; // 0x4E - min -const uint8_t MAX_VALUES_EQUIV_V_I_PRESSURE = 79; // 0x4F - ratio V mA kPa -const uint8_t MAX_MAF_RATE = 80; // 0x50 - g/s -const uint8_t FUEL_TYPE = 81; // 0x51 - ref table -const uint8_t ETHONOL_FUEL_PERCENT = 82; // 0x52 - % -const uint8_t ABS_EVAP_SYS_VAPOR_PRESSURE = 83; // 0x53 - kPa -const uint8_t EVAP_SYS_VAPOR_PRESSURE = 84; // 0x54 - Pa -const uint8_t SHORT_TERM_SEC_OXY_SENS_TRIM_1_3 = 85; // 0x55 - % -const uint8_t LONG_TERM_SEC_OXY_SENS_TRIM_1_3 = 86; // 0x56 - % -const uint8_t SHORT_TERM_SEC_OXY_SENS_TRIM_2_4 = 87; // 0x57 - % -const uint8_t LONG_TERM_SEC_OXY_SENS_TRIM_2_4 = 88; // 0x58 - % -const uint8_t FUEL_RAIL_ABS_PRESSURE = 89; // 0x59 - kPa -const uint8_t RELATIVE_ACCELERATOR_PEDAL_POS = 90; // 0x5A - % -const uint8_t HYBRID_BATTERY_REMAINING_LIFE = 91; // 0x5B - % -const uint8_t ENGINE_OIL_TEMP = 92; // 0x5C - °C -const uint8_t FUEL_INJECTION_TIMING = 93; // 0x5D - ° -const uint8_t ENGINE_FUEL_RATE = 94; // 0x5E - L/h -const uint8_t EMISSION_REQUIREMENTS = 95; // 0x5F - bit encoded - -const uint8_t SUPPORTED_PIDS_61_80 = 96; // 0x60 - bit encoded -const uint8_t DEMANDED_ENGINE_PERCENT_TORQUE = 97; // 0x61 - % -const uint8_t ACTUAL_ENGINE_TORQUE = 98; // 0x62 - % -const uint8_t ENGINE_REFERENCE_TORQUE = 99; // 0x63 - Nm -const uint8_t ENGINE_PERCENT_TORQUE_DATA = 100; // 0x64 - % -const uint8_t AUX_INPUT_OUTPUT_SUPPORTED = 101; // 0x65 - bit encoded -``` - -# List of AT Commands: -(https://www.sparkfun.com/datasheets/Widgets/ELM327_AT_Commands.pdf) -```C++ -const char * const DISP_DEVICE_DESCRIPT = "AT @1"; // General -const char * const DISP_DEVICE_ID = "AT @2"; // General -const char * const STORE_DEVICE_ID = "AT @3 %s"; // General -const char * const REPEAT_LAST_COMMAND = "AT \r"; // General -const char * const ALLOW_LONG_MESSAGES = "AT AL"; // General -const char * const AUTOMATIC_RECEIVE = "AT AR"; // OBD -const char * const ADAPTIVE_TIMING_OFF = "AT AT0"; // OBD -const char * const ADAPTIVE_TIMING_AUTO_1 = "AT AT1"; // OBD -const char * const ADAPTIVE_TIMING_AUTO_2 = "AT AT2"; // OBD -const char * const DUMP_BUFFER = "AT BD"; // OBD -const char * const BYPASS_INIT_SEQUENCE = "AT BI"; // OBD -const char * const TRY_BAUD_DIVISOR = "AT BRD %s"; // General -const char * const SET_HANDSHAKE_TIMEOUT = "AT BRT %s"; // General -const char * const CAN_AUTO_FORMAT_OFF = "AT CAF0"; // CAN -const char * const CAN_AUTO_FORMAT_ON = "AT CAF1"; // CAN -const char * const CAN_EXTENDED_ADDRESS_OFF = "AT CEA"; // CAN -const char * const USE_CAN_EXTENDED_ADDRESS = "AT CEA %s"; // CAN -const char * const SET_ID_FILTER = "AT CF %s"; // CAN -const char * const CAN_FLOW_CONTROL_OFF = "AT CFC0"; // CAN -const char * const CAN_FLOW_CONTROL_ON = "AT CFC1"; // CAN -const char * const SET_ID_MASK = "AT CM %s"; // CAN -const char * const SET_CAN_PRIORITY = "AT CP %s"; // CAN -const char * const SHOW_CAN_STATUS = "AT CS"; // CAN -const char * const CAN_SILENT_MODE_OFF = "AT CSM0"; // CAN -const char * const CAN_SILENT_MODE_ON = "AT CSM1"; // CAN -const char * const CALIBRATE_VOLTAGE_CUSTOM = "AT CV %s"; // Volts -const char * const RESTORE_CV_TO_FACTORY = "AT CV 0000"; // Volts -const char * const SET_ALL_TO_DEFAULTS = "AT D"; // General -const char * const DISP_DLC_OFF = "AT D0"; // CAN -const char * const DISP_DLC_ON = "AT D1"; // CAN -const char * const MONITOR_FOR_DM1_MESSAGES = "AT DM1"; // J1939 -const char * const DISP_CURRENT_PROTOCOL = "AT DP"; // OBD -const char * const DISP_CURRENT_PROTOCOL_NUM = "AT DPN"; // OBD -const char * const ECHO_OFF = "AT E0"; // General -const char * const ECHO_ON = "AT E1"; // General -const char * const FLOW_CONTROL_SET_DATA_TO = "AT FC SD %s"; // CAN -const char * const FLOW_CONTROL_SET_HEAD_TO = "AT FC SH %s"; // CAN -const char * const FLOW_CONTROL_SET_MODE_TO = "AT FC SM %s"; // CAN -const char * const FORGE_EVENTS = "AT FE"; // General -const char * const PERFORM_FAST_INIT = "AT FI"; // ISO -const char * const HEADERS_OFF = "AT H0"; // OBD -const char * const HEADERS_ON = "AT H1"; // OBD -const char * const DISP_ID = "AT I"; // General -const char * const SET_ISO_BAUD_10400 = "AT IB 10"; // ISO -const char * const SET_ISO_BAUD_4800 = "AT IB 48"; // ISO -const char * const SET_ISO_BAUD_9600 = "AT IB 96"; // ISO -const char * const IFR_VAL_FROM_HEADER = "AT IFR H"; // J1850 -const char * const IFR_VAL_FROM_SOURCE = "AT IFR S"; // J1850 -const char * const IFRS_OFF = "AT IFR0"; // J1850 -const char * const IFRS_AUTO = "AT IFR1"; // J1850 -const char * const IFRS_ON = "AT IFR2"; // J1850 -const char * const IREAD_IGNMON_INPUT_LEVEL = "AT IGN"; // Other -const char * const SET_ISO_SLOW_INIT_ADDRESS = "AT IIA %s"; // ISO -const char * const USE_J1939_ELM_DATA_FORMAT = "AT JE"; // J1850 -const char * const J1939_HEAD_FORMAT_OFF = "AT JHF0"; // J1850 -const char * const J1939_HEAD_FORMAT_ON = "AT JHF1"; // J1850 -const char * const USE_J1939_SAE_DATA_FORMAT = "AT JS"; // J1850 -const char * const SET_J1939_TIMER_X_TO_1X = "AT JTM1"; // J1850 -const char * const SET_J1939_TIMER_X_TO_5X = "AT JTM5"; // J1850 -const char * const DISP_KEY_WORDS = "AT KW"; // ISO -const char * const KEY_WORD_CHECKING_OFF = "AT KW0"; // ISO -const char * const KEY_WORD_CHECKING_ON = "AT KW1"; // ISO -const char * const LINEFEEDS_OFF = "AT L0"; // General -const char * const LINEFEEDS_ON = "AT L1"; // General -const char * const LOW_POWER_MODE = "AT LP"; // General -const char * const MEMORY_OFF = "AT M0"; // General -const char * const MEMORY_ON = "AT M1"; // General -const char * const MONITOR_ALL = "AT MA"; // OBD -const char * const MONITOR_FOR_PGN = "AT MP %s"; // J1939 -const char * const MONITOR_FOR_RECEIVER = "AT MR %s"; // OBD -const char * const MONITOR_FOR_TRANSMITTER = "AT MT %s"; // OBD -const char * const NORMAL_LENGTH_MESSAGES = "AT NL"; // OBD -const char * const SET_PROTO_OPTIONS_AND_BAUD = "AT PB %s"; // OBD -const char * const PROTOCOL_CLOSE = "AT PC"; // OBD -const char * const ALL_PROG_PARAMS_OFF = "AT PP FF OFF"; // PPs -const char * const ALL_PROG_PARAMS_ON = "AT PP FF ON"; // PPs -const char * const SET_PROG_PARAM_OFF = "AT PP %s OFF"; // PPs -const char * const SET_PROG_PARAM_ON = "AT PP %s ON"; // PPs -const char * const SET_PROG_PARAM_VAL = "AT PP %s SV %s"; // PPs -const char * const DISP_PP_SUMMARY = "AT PPS"; // PPs -const char * const RESPONSES_OFF = "AT R0"; // OBD -const char * const RESPONSES_ON = "AT R1"; // OBD -const char * const SET_RECEIVE_ADDRESS_TO = "AT RA %s"; // OBD -const char * const READ_STORED_DATA = "AT RD"; // General -const char * const SEND_RTR_MESSAGE = "AT RTR"; // CAN -const char * const READ_VOLTAGE = "AT RV"; // Volts -const char * const PRINTING_SPACES_OFF = "AT S0"; // OBD -const char * const PRINTING_SPACES_ON = "AT S1"; // OBD -const char * const STORE_DATA_BYTE = "AT SD "; // General -const char * const SET_HEADER = "AT SH %s"; // OBD -const char * const PERFORM_SLOW_INIT = "AT SI"; // ISO -const char * const SET_PROTOCOL_TO_AUTO_H_SAVE = "AT SP A%c"; // OBD -const char * const SET_PROTOCOL_TO_H_SAVE = "AT SP %c"; // OBD -const char * const SET_PROTOCOL_TO_AUTO_SAVE = "AT SP 00"; // OBD -const char * const SET_REC_ADDRESS = "AT SR %s"; // OBD -const char * const SET_STANDARD_SEARCH_ORDER = "AT SS"; // OBD -const char * const SET_TIMEOUT_TO_H_X_4MS = "AT ST %s"; // OBD -const char * const SET_WAKEUP_TO_H_X_20MS = "AT SW %s"; // ISO -const char * const SET_TESTER_ADDRESS_TO = "AT TA %s"; // OBD -const char * const TRY_PROT_H_AUTO_SEARCH = "AT TP A%s"; // OBD -const char * const TRY_PROT_H = "AT TP %s"; // OBD -const char * const VARIABLE_DLC_OFF = "AT V0"; // CAN -const char * const VARIABLE_DLC_ON = "AT V1"; // CAN -const char * const SET_WAKEUP_MESSAGE = "AT WM"; // ISO -const char * const WARM_START = "AT WS"; // General -const char * const RESET_ALL = "AT Z"; // General -``` diff --git a/examples/Arduino_hardware_serial_test/Arduino_hardware_serial_test.ino b/examples/Arduino_hardware_serial_test/Arduino_hardware_serial_test.ino index 559dbe8..733b0aa 100644 --- a/examples/Arduino_hardware_serial_test/Arduino_hardware_serial_test.ino +++ b/examples/Arduino_hardware_serial_test/Arduino_hardware_serial_test.ino @@ -36,11 +36,11 @@ void loop() { float tempRPM = myELM327.rpm(); - if (myELM327.status == ELM_SUCCESS) + if (myELM327.nb_rx_state == ELM_SUCCESS) { rpm = (uint32_t)tempRPM; Serial.print("RPM: "); Serial.println(rpm); } - else + else if (myELM327.nb_rx_state != ELM_GETTING_MSG) myELM327.printError(); } diff --git a/examples/Arduino_software_serial_test/Arduino_software_serial_test.ino b/examples/Arduino_software_serial_test/Arduino_software_serial_test.ino index 190a243..30f42e4 100644 --- a/examples/Arduino_software_serial_test/Arduino_software_serial_test.ino +++ b/examples/Arduino_software_serial_test/Arduino_software_serial_test.ino @@ -38,11 +38,11 @@ void loop() { float tempRPM = myELM327.rpm(); - if (myELM327.status == ELM_SUCCESS) + if (myELM327.nb_rx_state == ELM_SUCCESS) { rpm = (uint32_t)tempRPM; Serial.print("RPM: "); Serial.println(rpm); } - else + else if (myELM327.nb_rx_state != ELM_GETTING_MSG) myELM327.printError(); } diff --git a/examples/ESP32_Bluetooth_Serial/ESP32_Bluetooth_Serial.ino b/examples/ESP32_Bluetooth_Serial/ESP32_Bluetooth_Serial.ino index a19ec93..5dd0ecb 100644 --- a/examples/ESP32_Bluetooth_Serial/ESP32_Bluetooth_Serial.ino +++ b/examples/ESP32_Bluetooth_Serial/ESP32_Bluetooth_Serial.ino @@ -44,11 +44,11 @@ void loop() { float tempRPM = myELM327.rpm(); - if (myELM327.status == ELM_SUCCESS) + if (myELM327.nb_rx_state == ELM_SUCCESS) { rpm = (uint32_t)tempRPM; Serial.print("RPM: "); Serial.println(rpm); } - else + else if (myELM327.nb_rx_state != ELM_GETTING_MSG) myELM327.printError(); } diff --git a/examples/ESP32_Bluetooth_multiple_pids/ESP32_Bluetooth_multiple_pids.ino b/examples/ESP32_Bluetooth_multiple_pids/ESP32_Bluetooth_multiple_pids.ino new file mode 100644 index 0000000..4668de2 --- /dev/null +++ b/examples/ESP32_Bluetooth_multiple_pids/ESP32_Bluetooth_multiple_pids.ino @@ -0,0 +1,82 @@ +#include "BluetoothSerial.h" +#include "ELMduino.h" + +BluetoothSerial SerialBT; +#define ELM_PORT SerialBT +#define DEBUG_PORT Serial + +ELM327 myELM327; + +typedef enum { ENG_RPM, + SPEED } obd_pid_states; +obd_pid_states obd_state = ENG_RPM; + +float rpm = 0; +float mph = 0; + +void setup() +{ + DEBUG_PORT.begin(115200); + // SerialBT.setPin("1234"); + ELM_PORT.begin("ArduHUD", true); + + if (!ELM_PORT.connect("OBDII")) + { + DEBUG_PORT.println("Couldn't connect to OBD scanner - Phase 1"); + while (1) + ; + } + + if (!myELM327.begin(ELM_PORT, true, 2000)) + { + DEBUG_PORT.println("Couldn't connect to OBD scanner - Phase 2"); + while (1) + ; + } + + DEBUG_PORT.println("Connected to ELM327"); +} + +void loop() +{ + switch (obd_state) + { + case ENG_RPM: + { + rpm = myELM327.rpm(); + + if (myELM327.nb_rx_state == ELM_SUCCESS) + { + DEBUG_PORT.print("rpm: "); + DEBUG_PORT.println(rpm); + obd_state = SPEED; + } + else if (myELM327.nb_rx_state != ELM_GETTING_MSG) + { + myELM327.printError(); + obd_state = SPEED; + } + + break; + } + + case SPEED: + { + mph = myELM327.mph(); + + if (myELM327.nb_rx_state == ELM_SUCCESS) + { + DEBUG_PORT.print("mph: "); + DEBUG_PORT.println(mph); + obd_state = ENG_RPM; + } + else if (myELM327.nb_rx_state != ELM_GETTING_MSG) + { + myELM327.printError(); + obd_state = ENG_RPM; + } + + break; + } + } +} diff --git a/examples/ESP32_CheckPIDs_1_20/ESP32_CheckPIDs_1_20.ino b/examples/ESP32_CheckPIDs_1_20/ESP32_CheckPIDs_1_20.ino new file mode 100644 index 0000000..735bbda --- /dev/null +++ b/examples/ESP32_CheckPIDs_1_20/ESP32_CheckPIDs_1_20.ino @@ -0,0 +1,53 @@ +#include "BluetoothSerial.h" +#include "ELMduino.h" + +BluetoothSerial SerialBT; +#define ELM_PORT SerialBT +#define DEBUG_PORT Serial + +ELM327 myELM327; + +uint32_t rpm = 0; + +void setup() +{ +#if LED_BUILTIN + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, LOW); +#endif + + DEBUG_PORT.begin(115200); + // SerialBT.setPin("1234"); + ELM_PORT.begin("ArduHUD", true); + + if (!ELM_PORT.connect("OBDII")) + { + DEBUG_PORT.println("Couldn't connect to OBD scanner - Phase 1"); + while (1) + ; + } + + if (!myELM327.begin(ELM_PORT, true, 2000)) + { + Serial.println("Couldn't connect to OBD scanner - Phase 2"); + while (1) + ; + } + + Serial.println("Connected to ELM327"); +} + +void loop() +{ + uint32_t pids = myELM327.supportedPIDs_1_20(); + + if (myELM327.nb_rx_state == ELM_SUCCESS) + { + Serial.print("Supported PIDS: "); Serial.println(pids); + delay(10000); + } + else if (myELM327.nb_rx_state != ELM_GETTING_MSG) + { + myELM327.printError(); + } +} diff --git a/examples/ESP32_Check_DTC/ESP32_Check_DTC.ino b/examples/ESP32_Check_DTC/ESP32_Check_DTC.ino new file mode 100644 index 0000000..f818cff --- /dev/null +++ b/examples/ESP32_Check_DTC/ESP32_Check_DTC.ino @@ -0,0 +1,133 @@ +#include "ELMduino.h" +#include + +#define ELM_PORT SerialBT +#define DEBUG_PORT Serial + +typedef enum +{ MILSTATUS, + DTCCODES +} dtc_states; + +BluetoothSerial SerialBT; +ELM327 myELM327; +dtc_states dtc_state = MILSTATUS; +uint8_t numCodes = 0; +uint8_t milStatus =0; + +void setup() +{ + DEBUG_PORT.begin(115200); + ELM_PORT.begin("ArduHUD", true); + ELM_PORT.setPin("1234"); + + DEBUG_PORT.println("Starting connection..."); + if (!ELM_PORT.connect("ELMULATOR")) + { + DEBUG_PORT.println("Couldn't connect to OBD scanner - Phase 1"); + while (1) + ; + } + + if (!myELM327.begin(ELM_PORT)) + { + DEBUG_PORT.println("ELM327 Couldn't connect to ECU - Phase 2"); + while (1) + ; + } + + DEBUG_PORT.println("Connected to ELM327"); + + delay(1000); + + // Demonstration of calling currentDTCCodes in blocking (default) mode + // This is the simplest use case. Just scan for codes without first + // checking if MIL is on or how many codes are present. + + DEBUG_PORT.println("Performing DTC check in blocking mode..."); + + myELM327.currentDTCCodes(); + if (myELM327.nb_rx_state == ELM_SUCCESS) + { + DEBUG_PORT.println("Current DTCs found: "); + + for (int i = 0; i < myELM327.DTC_Response.codesFound; i++) + { + DEBUG_PORT.println(myELM327.DTC_Response.codes[i]); + } + delay(10000); // Pause for 10 sec after successful fetch of DTC codes. + } + + else if (myELM327.nb_rx_state != ELM_GETTING_MSG) + { + myELM327.printError(); + } +} + +void loop() +{ + // This is the typical use case: First check if any codes are present, and then make a request to get them. + // monitorStatus() is a non-blocking call and must be called repeatedly until a response is found. + switch (dtc_state) + { + case MILSTATUS: + myELM327.monitorStatus(); // Gets the number of current DTC codes present + + if (myELM327.nb_rx_state == ELM_SUCCESS) + { + // We are only interested in the third byte of the response that + // encodes the MIL status and number of codes present + milStatus = (myELM327.responseByte_2 & 0x80); + numCodes = (myELM327.responseByte_2 - 0x80); + + DEBUG_PORT.print("MIL (Check Engine Light) is: "); + if (milStatus) + { + DEBUG_PORT.println("ON."); + } + else + { + DEBUG_PORT.println("OFF."); + } + + DEBUG_PORT.print("Number of codes present: "); + DEBUG_PORT.println(numCodes); + dtc_state = DTCCODES; + break; + } + else if (myELM327.nb_rx_state != ELM_GETTING_MSG) + { + myELM327.printError(); + dtc_state = DTCCODES; + } + break; + + case DTCCODES: // If current DTC codes were found in previous request, then retrieve the codes + if (numCodes > 0) + { + myELM327.currentDTCCodes(false); // Call in NB mode + + if (myELM327.nb_rx_state == ELM_SUCCESS) + { + DEBUG_PORT.println("Current DTCs found: "); + + for (int i = 0; i < myELM327.DTC_Response.codesFound; i++) + { + DEBUG_PORT.println(myELM327.DTC_Response.codes[i]); + } + dtc_state = MILSTATUS; + delay(10000); // Pause for 10 sec after successful fetch of DTC codes. + } + + else if (myELM327.nb_rx_state != ELM_GETTING_MSG) + { + myELM327.printError(); + dtc_state = MILSTATUS; + } + } + break; + + default: + break; + } +} diff --git a/examples/ESP32_Check_MIL/ESP32_Check_MIL.ino b/examples/ESP32_Check_MIL/ESP32_Check_MIL.ino new file mode 100644 index 0000000..1cfaaf7 --- /dev/null +++ b/examples/ESP32_Check_MIL/ESP32_Check_MIL.ino @@ -0,0 +1,79 @@ +#include "ELMduino.h" +#include + +#define ELM_PORT SerialBT +#define DEBUG_PORT Serial + +typedef enum +{ MILSTATUS, + DTCCODES +} dtc_states; + +BluetoothSerial SerialBT; +ELM327 myELM327; +dtc_states dtc_state = MILSTATUS; +uint8_t numCodes = 0; +uint8_t milStatus =0; + +// This example program demonstrates how to use ELMduino to check the MIL (Check Engine Light) +// status and the number of current DTC codes that are present. + +void setup() +{ + DEBUG_PORT.begin(115200); + ELM_PORT.begin("ArduHUD", true); + ELM_PORT.setPin("1234"); + + DEBUG_PORT.println("Starting connection..."); + if (!ELM_PORT.connect("ELMULATOR")) + { + DEBUG_PORT.println("Couldn't connect to ELM327 - Phase 1"); + while (1) + ; + } + + if (!myELM327.begin(ELM_PORT)) + { + DEBUG_PORT.println("ELM327 Couldn't connect to ECU - Phase 2"); + while (1) + ; + } + + DEBUG_PORT.println("Connected to ELM327"); + +} + +void loop() +{ + myELM327.monitorStatus(); // Gets the number of current DTC codes present + + if (myELM327.nb_rx_state == ELM_SUCCESS) + { + // We are only interested in the third byte of the response that + // encodes the MIL status and number of codes present + milStatus = (myELM327.responseByte_2 & 0x80); + numCodes = (myELM327.responseByte_2 - 0x80); + + DEBUG_PORT.print("MIL (Check Engine Light) is: "); + if (milStatus) + { + DEBUG_PORT.println("ON."); + } + else + { + DEBUG_PORT.println("OFF."); + } + + DEBUG_PORT.print("Number of codes present: "); + DEBUG_PORT.println(numCodes); + + DEBUG_PORT.println("Checking again in 10 seconds.\n\n"); + delay(10000); // Wait 10 seconds before we query again. + } + else if (myELM327.nb_rx_state != ELM_GETTING_MSG) + { + myELM327.printError(); + DEBUG_PORT.println("Trying again in 10 seconds.\n\n"); + delay(10000); + } +} diff --git a/examples/ESP32_Check_PID_Support/ESP32_Check_PID_Support.ino b/examples/ESP32_Check_PID_Support/ESP32_Check_PID_Support.ino new file mode 100644 index 0000000..d6d74a2 --- /dev/null +++ b/examples/ESP32_Check_PID_Support/ESP32_Check_PID_Support.ino @@ -0,0 +1,59 @@ +#include "BluetoothSerial.h" +#include "ELMduino.h" + +BluetoothSerial SerialBT; +#define ELM_PORT SerialBT +#define DEBUG_PORT Serial + +ELM327 myELM327; + +uint32_t rpm = 0; + +void setup() +{ +#if LED_BUILTIN + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, LOW); +#endif + + DEBUG_PORT.begin(115200); + // SerialBT.setPin("1234"); + ELM_PORT.begin("ArduHUD", true); + + if (!ELM_PORT.connect("ELMULATOR")) + { + DEBUG_PORT.println("Couldn't connect to OBD scanner - Phase 1"); + while (1) + ; + } + + if (!myELM327.begin(ELM_PORT, true, 2000)) + { + DEBUG_PORT.println("Couldn't connect to OBD scanner - Phase 2"); + while (1) + ; + } + + Serial.println("Connected to ELM327"); +} + +void loop() +{ + uint8_t pid = ENGINE_RPM; + bool pidOK = myELM327.isPidSupported(pid); + if (myELM327.nb_rx_state == ELM_SUCCESS) + { + if(pidOK) { + Serial.print("PID "); Serial.print(pid); Serial.println(" is supported"); + } + else + { + Serial.print("PID "); Serial.print(pid); Serial.println(" is not supported"); + } + delay(10000); + } + else if (myELM327.nb_rx_state != ELM_GETTING_MSG) + { + myELM327.printError(); + } +} diff --git a/examples/ESP32_Check_Voltage/ESP32_Check_Voltage.ino b/examples/ESP32_Check_Voltage/ESP32_Check_Voltage.ino new file mode 100644 index 0000000..06bb5ab --- /dev/null +++ b/examples/ESP32_Check_Voltage/ESP32_Check_Voltage.ino @@ -0,0 +1,52 @@ +#include "BluetoothSerial.h" +#include "ELMduino.h" + +BluetoothSerial SerialBT; +#define ELM_PORT SerialBT +#define DEBUG_PORT Serial + +ELM327 myELM327; + +void setup() +{ +#if LED_BUILTIN + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, LOW); +#endif + + DEBUG_PORT.begin(115200); + // SerialBT.setPin("1234"); + ELM_PORT.begin("ArduHUD", true); + + if (!ELM_PORT.connect("OBDII")) + { + DEBUG_PORT.println("Couldn't connect to OBD scanner - Phase 1"); + while (1) + ; + } + + if (!myELM327.begin(ELM_PORT, true, 2000)) + { + Serial.println("Couldn't connect to OBD scanner - Phase 2"); + while (1) + ; + } + + Serial.println("Connected to ELM327"); +} + +void loop() +{ + float volts = myELM327.batteryVoltage(); + + if (myELM327.nb_rx_state == ELM_SUCCESS) + { + Serial.print("Battery Voltage: "); + Serial.println(volts); + delay(10000); + } + else if (myELM327.nb_rx_state != ELM_GETTING_MSG) + { + myELM327.printError(); + } +} diff --git a/examples/ESP32_CustomHeader/ESP32_CustomHeader.ino b/examples/ESP32_CustomHeader/ESP32_CustomHeader.ino new file mode 100644 index 0000000..a47b5cd --- /dev/null +++ b/examples/ESP32_CustomHeader/ESP32_CustomHeader.ino @@ -0,0 +1,91 @@ +/* + +This example shows how to perform several opearations for using custom (non-OBD2 standard) +queries. + +Custom Header +We perform a query that requires a custom header to be sent first. The custom header in this +case determines which ECU in the vehicle's system we want to query. + +Custom PID +The query sent to the vehicle is also a custom (non OBD2 standard) PID using the 0x22 +(enhanced data) mode. + +Manually Processing Response +In this example, we manually extract the data value from the query response and perform +some post-processing to calculate the correct value. + +Managing Query State +We also demonstrate managing the query state used by the loop() method. This is typically +managed internally by ELMduino for standard PID methods. + +Customize all the things! +The header value, PID, data value bytes and adjustment formula are generally unique for +each different vehicle and PID. You will need to source the correct values for those for +your specific vehicle. This example almost certainly will not work for you "as-is". + +*/ + +#include "BluetoothSerial.h" +#include "ELMduino.h" + +BluetoothSerial SerialBT; +#define ELM_PORT SerialBT +#define DEBUG_PORT Serial + +ELM327 myELM327; + +int nb_query_state = SEND_COMMAND; // Set the inital query state ready to send a command + +void setup() +{ + DEBUG_PORT.begin(115200); + // SerialBT.setPin("1234"); + ELM_PORT.begin("ArduHUD", true); + + if (!ELM_PORT.connect("OBDII")) + { + DEBUG_PORT.println("Couldn't connect to ELM327 device."); + while (1); + } + + if (!myELM327.begin(ELM_PORT, true, 2000)) + { + Serial.println("ELM327 device couldn't connect to ECU."); + while (1); + } + + Serial.println("Connected to ELM327"); + + // Set a custom header using ATSH command. + myELM327.sendCommand_Blocking("ATSH DA18F1"); +} + +void loop() +{ + if (nb_query_state == SEND_COMMAND) // We are ready to send a new command + { + myELM327.sendCommand("2204FE"); // Send the custom PID commnad + nb_query_state = WAITING_RESP; // Set the query state so we are waiting for response + } + else if (nb_query_state == WAITING_RESP) // Our query has been sent, check for a response + { + myELM327.get_response(); // Each time through the loop we will check again + } + + if (myELM327.nb_rx_state == ELM_SUCCESS) // Our response is fully received, let's get our data + { + int A = myELM327.payload[6]; // Parse the temp value A from the response + float temperature = A - 40; // Apply the formula for the temperature + Serial.println(temperature); // Print the adjusted value + nb_query_state = SEND_COMMAND; // Reset the query state for the next command + delay(5000); // Wait 5 seconds until we query again + } + + else if (myELM327.nb_rx_state != ELM_GETTING_MSG) + { // If state == ELM_GETTING_MSG, response is not yet complete. Restart the loop. + nb_query_state = SEND_COMMAND; // Reset the query state for the next command + myELM327.printError(); + delay(5000); // Wait 5 seconds until we query again + } +} \ No newline at end of file diff --git a/examples/ESP32_CustomMultilinePID/ESP32_CustomMultilinePID.ino b/examples/ESP32_CustomMultilinePID/ESP32_CustomMultilinePID.ino new file mode 100644 index 0000000..5ead1c0 --- /dev/null +++ b/examples/ESP32_CustomMultilinePID/ESP32_CustomMultilinePID.ino @@ -0,0 +1,83 @@ +/* + +This example shows how to query and process OBD2 standard PIDs that +return a multiline response and require custom processing. + +Processing Response with a custom calculation function +In this example, we manually extract the data value from the query response and perform +some post-processing to calculate the correct value. + +Managing Query State +We also demonstrate managing the query state used by the loop() method. This is typically +managed internally by ELMduino for standard PID methods. + +*/ + +#include "BluetoothSerial.h" +#include "ELMduino.h" + +BluetoothSerial SerialBT; +#define ELM_PORT SerialBT +#define DEBUG_PORT Serial + +ELM327 myELM327; + +int nb_query_state = SEND_COMMAND; // Set the inital query state ready to send a command + + +// Applies calculation formula for PID 0x22 (Fuel Rail Pressure) +double calcDPF() { + double B = myELM327.payload[6]; + double C = myELM327.payload[5]; + return ((B * 256) + C) / 100; +} + +void setup() +{ + DEBUG_PORT.begin(115200); + // SerialBT.setPin("1234"); + ELM_PORT.begin("ArduHUD", true); + + if (!ELM_PORT.connect("OBDII")) + { + DEBUG_PORT.println("Couldn't connect to ELM327 device."); + while (1); + } + + if (!myELM327.begin(ELM_PORT, true, 2000)) + { + Serial.println("ELM327 device couldn't connect to ECU."); + while (1); + } + + Serial.println("Connected to ELM327"); + +} + +void loop() +{ + if (nb_query_state == SEND_COMMAND) // We are ready to send a new command + { + myELM327.sendCommand("017A"); // Send the PID commnad + nb_query_state = WAITING_RESP; // Set the query state so we are waiting for response + } + else if (nb_query_state == WAITING_RESP) // Our query has been sent, check for a response + { + myELM327.get_response(); // Each time through the loop we will check again + } + + if (myELM327.nb_rx_state == ELM_SUCCESS) // Our response is fully received, let's get our data + { + double dpf = myELM327.conditionResponse(calcDPF); // Apply the formula for dpf + Serial.println(dpf); // Print the adjusted value + nb_query_state = SEND_COMMAND; // Reset the query state for the next command + delay(5000); // Wait 5 seconds until we query again + } + + else if (myELM327.nb_rx_state != ELM_GETTING_MSG) + { // If state == ELM_GETTING_MSG, response is not yet complete. Restart the loop. + nb_query_state = SEND_COMMAND; // Reset the query state for the next command + myELM327.printError(); + delay(5000); // Wait 5 seconds until we query again + } +} \ No newline at end of file diff --git a/examples/ESP32_Reset_DTC/ESP32_Reset_DTC.ino b/examples/ESP32_Reset_DTC/ESP32_Reset_DTC.ino new file mode 100644 index 0000000..f334c58 --- /dev/null +++ b/examples/ESP32_Reset_DTC/ESP32_Reset_DTC.ino @@ -0,0 +1,43 @@ +#include "BluetoothSerial.h" +#include "ELMduino.h" + +BluetoothSerial SerialBT; +#define ELM_PORT SerialBT +#define DEBUG_PORT Serial + +ELM327 myELM327; + +void setup() +{ + DEBUG_PORT.begin(115200); + // SerialBT.setPin("1234"); + ELM_PORT.begin("ESP32", true); + + if (!ELM_PORT.connect("OBDII")) + { + DEBUG_PORT.println("Couldn't connect to OBD scanner - Phase 1"); + while (1) + ; + } + + if (!myELM327.begin(ELM_PORT, true, 2000)) + { + DEBUG_PORT.println("Couldn't connect to OBD scanner - Phase 2"); + while (1) + ; + } + + DEBUG_PORT.println("Connected to ELM327"); + DEBUG_PORT.println("Sending DTC reset command..."); + + if (myELM327.resetDTC()) + { + DEBUG_PORT.println("DTC reset successful."); + } + else + { + DEBUG_PORT.println("DTC reset failed."); + } +} + +void loop(){} diff --git a/examples/ESP32_Test_Functions/ESP32_test_functions.ino b/examples/ESP32_Test_Functions/ESP32_test_functions.ino new file mode 100644 index 0000000..4984a9f --- /dev/null +++ b/examples/ESP32_Test_Functions/ESP32_test_functions.ino @@ -0,0 +1,173 @@ +#include "BluetoothSerial.h" +#include "ELMduino.h" + +BluetoothSerial SerialBT; +#define ELM_PORT SerialBT +#define DEBUG_PORT Serial + +ELM327 myELM327; +uint8_t current_pid = 0; +int nb_query_state = SEND_COMMAND; + +// Define a list of PIDs to test +const uint16_t pidsToTest[] = { + SUPPORTED_PIDS_1_20, // 0x00 + MONITOR_STATUS_SINCE_DTC_CLEARED, // 0x01 + FREEZE_DTC, // 0x02 + FUEL_SYSTEM_STATUS, // 0x03 + ENGINE_LOAD, // 0x04 + ENGINE_COOLANT_TEMP, // 0x05 + SHORT_TERM_FUEL_TRIM_BANK_1, // 0x06 + LONG_TERM_FUEL_TRIM_BANK_1, // 0x07 + SHORT_TERM_FUEL_TRIM_BANK_2, // 0x08 + LONG_TERM_FUEL_TRIM_BANK_2, // 0x09 + FUEL_PRESSURE, // 0x0A + INTAKE_MANIFOLD_ABS_PRESSURE, // 0x0B + ENGINE_RPM, // 0x0C + VEHICLE_SPEED, // 0x0D + TIMING_ADVANCE, // 0x0E + INTAKE_AIR_TEMP, // 0x0F + MAF_FLOW_RATE, // 0x10 + THROTTLE_POSITION, // 0x11 + COMMANDED_SECONDARY_AIR_STATUS, // 0x12 + OXYGEN_SENSORS_PRESENT_2_BANKS, // 0x13 + OXYGEN_SENSOR_1_A, // 0x14 + OXYGEN_SENSOR_2_A, // 0x15 + OXYGEN_SENSOR_3_A, // 0x16 + OXYGEN_SENSOR_4_A, // 0x17 + OXYGEN_SENSOR_5_A, // 0x18 + OXYGEN_SENSOR_6_A, // 0x19 + OXYGEN_SENSOR_7_A, // 0x1A + OXYGEN_SENSOR_8_A, // 0x1B + OBD_STANDARDS, // 0x1C + OXYGEN_SENSORS_PRESENT_4_BANKS, // 0x1D + AUX_INPUT_STATUS, // 0x1E + RUN_TIME_SINCE_ENGINE_START, // 0x1F + SUPPORTED_PIDS_21_40, // 0x20 + DISTANCE_TRAVELED_WITH_MIL_ON, // 0x21 + FUEL_RAIL_PRESSURE, // 0x22 + FUEL_RAIL_GUAGE_PRESSURE, // 0x23 + OXYGEN_SENSOR_1_B, // 0x24 + OXYGEN_SENSOR_2_B, // 0x25 + OXYGEN_SENSOR_3_B, // 0x26 + OXYGEN_SENSOR_4_B, // 0x27 + OXYGEN_SENSOR_5_B, // 0x28 + OXYGEN_SENSOR_6_B, // 0x29 + OXYGEN_SENSOR_7_B, // 0x2A + OXYGEN_SENSOR_8_B, // 0x2B + COMMANDED_EGR, // 0x2C + EGR_ERROR, // 0x2D + COMMANDED_EVAPORATIVE_PURGE, // 0x2E + FUEL_TANK_LEVEL_INPUT, // 0x2F + WARM_UPS_SINCE_CODES_CLEARED, // 0x30 - count + DIST_TRAV_SINCE_CODES_CLEARED, // 0x31 - km + EVAP_SYSTEM_VAPOR_PRESSURE, // 0x32 - Pa + ABS_BAROMETRIC_PRESSURE, // 0x33 - kPa + OXYGEN_SENSOR_1_C, // 0x34 - ratio mA + OXYGEN_SENSOR_2_C, // 0x35 - ratio mA + OXYGEN_SENSOR_3_C, // 0x36 - ratio mA + OXYGEN_SENSOR_4_C, // 0x37 - ratio mA + OXYGEN_SENSOR_5_C, // 0x38 - ratio mA + OXYGEN_SENSOR_6_C, // 0x39 - ratio mA + OXYGEN_SENSOR_7_C, // 0x3A - ratio mA + OXYGEN_SENSOR_8_C, // 0x3B - ratio mA + CATALYST_TEMP_BANK_1_SENSOR_1, // 0x3C - °C + CATALYST_TEMP_BANK_2_SENSOR_1, // 0x3D - °C + CATALYST_TEMP_BANK_1_SENSOR_2, // 0x3E - °C + CATALYST_TEMP_BANK_2_SENSOR_2, // 0x3F - °C + SUPPORTED_PIDS_41_60, // 0x40 - bit encoded + MONITOR_STATUS_THIS_DRIVE_CYCLE, // 0x41 - bit encoded + CONTROL_MODULE_VOLTAGE, // 0x42 - V + ABS_LOAD_VALUE, // 0x43 - % + FUEL_AIR_COMMANDED_EQUIV_RATIO, // 0x44 - ratio + RELATIVE_THROTTLE_POSITION, // 0x45 - % + AMBIENT_AIR_TEMP, // 0x46 - °C + ABS_THROTTLE_POSITION_B, // 0x47 - % + ABS_THROTTLE_POSITION_C, // 0x48 - % + ABS_THROTTLE_POSITION_D, // 0x49 - % + ABS_THROTTLE_POSITION_E, // 0x4A - % + ABS_THROTTLE_POSITION_F, // 0x4B - % + COMMANDED_THROTTLE_ACTUATOR, // 0x4C - % + TIME_RUN_WITH_MIL_ON, // 0x4D - min + TIME_SINCE_CODES_CLEARED, // 0x4E - min + MAX_VALUES_EQUIV_V_I_PRESSURE, // 0x4F - ratio V mA kPa + MAX_MAF_RATE, // 0x50 - g/s + FUEL_TYPE, // 0x51 + ETHANOL_FUEL_PERCENT, // 0x52 + ABS_EVAP_SYS_VAPOR_PRESSURE, // 0x53 + EVAP_SYS_VAPOR_PRESSURE, // 0x54 + SHORT_TERM_SEC_OXY_SENS_TRIM_1_3, // 0x55 + LONG_TERM_SEC_OXY_SENS_TRIM_1_3, // 0x56 + SHORT_TERM_SEC_OXY_SENS_TRIM_2_4, // 0x57 + LONG_TERM_SEC_OXY_SENS_TRIM_2_4, // 0x58 + FUEL_RAIL_ABS_PRESSURE, // 0x59 + RELATIVE_ACCELERATOR_PEDAL_POS, // 0x5A + HYBRID_BATTERY_REMAINING_LIFE, // 0x5B + ENGINE_OIL_TEMP, // 0x5C + FUEL_INJECTION_TIMING, // 0x5D + ENGINE_FUEL_RATE, // 0x5E + EMISSION_REQUIREMENTS, // 0x5F + SUPPORTED_PIDS_61_80, // 0x60 + DEMANDED_ENGINE_PERCENT_TORQUE, // 0x61 + ACTUAL_ENGINE_TORQUE, // 0x62 + ENGINE_REFERENCE_TORQUE, // 0x63 + ENGINE_PERCENT_TORQUE_DATA // 0x64 +}; +const uint8_t responseBytes[0xA9] = +{ + 4, 4, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, + 4, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, + 4, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 4, 4, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 1, + 4, 1, 1, 2, 5, 2, 5, 3, 3, 7, 5, 5, 5, 11, 9, 3, 10, 6, 5, 5, 5, 7, 7, 5, 9, 9, 7, 7, 9, 1, 1, 13, + 4, 41, 41, 9, 1, 10, 5, 5, 13, 41, 41, 7, 17, 1, 1, 7, 3, 5, 2, 3, 12, 9, 9, 6, 4, 17, 4, 2, 9 +}; + +void setup() +{ + DEBUG_PORT.begin(115200); + // SerialBT.setPin("1234"); + ELM_PORT.begin("ArduHUD", true); + + if (!ELM_PORT.connect("OBDII")) + { + DEBUG_PORT.println("Couldn't connect to OBD scanner - Phase 1"); + while (1) + ; + } + + if (!myELM327.begin(ELM_PORT, true, 2000)) + { + DEBUG_PORT.println("Couldn't connect to OBD scanner - Phase 2"); + while (1) + ; + } + + DEBUG_PORT.println("Connected to ELM327"); +} + + + +void loop() { + if (current_pid > (sizeof(pidsToTest)/sizeof(uint16_t)) - 1) + { + current_pid = 0; + } + uint16_t pid = pidsToTest[current_pid]; + double result = myELM327.processPID(0x01, pid, 1, responseBytes[current_pid], 1, 0); + + if (myELM327.nb_rx_state == ELM_SUCCESS) // Our response is fully received, let's get our data + { + nb_query_state = SEND_COMMAND; + DEBUG_PORT.print("Result: "); + DEBUG_PORT.println(result); + current_pid++; // Reset the query state for the next command + delay(500); // Wait 0.5 seconds until we query again + } + + else if (myELM327.nb_rx_state != ELM_GETTING_MSG) + { // If state == ELM_GETTING_MSG, response is not yet complete. Restart the loop. + nb_query_state = SEND_COMMAND; // Reset the query state for the next command + myELM327.printError(); + delay(500); // Wait 0.5 seconds until we query again + } + } diff --git a/examples/ESP32_WiFi/ESP32_WiFi.ino b/examples/ESP32_WiFi/ESP32_WiFi.ino index 705665f..7e5592c 100644 --- a/examples/ESP32_WiFi/ESP32_WiFi.ino +++ b/examples/ESP32_WiFi/ESP32_WiFi.ino @@ -54,11 +54,11 @@ void loop() { float tempRPM = myELM327.rpm(); - if (myELM327.status == ELM_SUCCESS) + if (myELM327.nb_rx_state == ELM_SUCCESS) { rpm = (uint32_t)tempRPM; Serial.print("RPM: "); Serial.println(rpm); } - else + else if (myELM327.nb_rx_state != ELM_GETTING_MSG) myELM327.printError(); } diff --git a/examples/ESP32_dual_core/ESP32_dual_core.ino b/examples/ESP32_dual_core/ESP32_dual_core.ino index 16a37c5..6b4f253 100644 --- a/examples/ESP32_dual_core/ESP32_dual_core.ino +++ b/examples/ESP32_dual_core/ESP32_dual_core.ino @@ -136,7 +136,7 @@ void read_rpm() Serial.print("RPM: "); Serial.println(rpm); previous_rpm_time = current_time; - if (obd.status != ELM_SUCCESS) + if ((obd.nb_rx_state != ELM_GETTING_MSG && obd.nb_rx_state != ELM_SUCCESS) { obd.printError(); } diff --git a/examples/ESP8266_WiFi/ESP8266_WiFi.ino b/examples/ESP8266_WiFi/ESP8266_WiFi.ino index 418f4fd..f29989b 100644 --- a/examples/ESP8266_WiFi/ESP8266_WiFi.ino +++ b/examples/ESP8266_WiFi/ESP8266_WiFi.ino @@ -54,11 +54,11 @@ void loop() { float tempRPM = myELM327.rpm(); - if (myELM327.status == ELM_SUCCESS) + if (myELM327.nb_rx_state == ELM_SUCCESS) { rpm = (uint32_t)tempRPM; Serial.print("RPM: "); Serial.println(rpm); } - else + else if (myELM327.nb_rx_state != ELM_GETTING_MSG) myELM327.printError(); } diff --git a/examples/multiple_pids/multiple_pids.ino b/examples/multiple_pids/multiple_pids.ino new file mode 100644 index 0000000..7a8fb49 --- /dev/null +++ b/examples/multiple_pids/multiple_pids.ino @@ -0,0 +1,96 @@ +#include "ELMduino.h" + + + + +#define ELM_PORT Serial1 + + + + +const bool DEBUG = true; +const int TIMEOUT = 2000; +const bool HALT_ON_FAIL = false; + + + + +ELM327 myELM327; + + + + +typedef enum { ENG_RPM, + SPEED } obd_pid_states; +obd_pid_states obd_state = ENG_RPM; + +float rpm = 0; +float mph = 0; + + + + +void setup() +{ + Serial.begin(115200); + ELM_PORT.begin(115200); + + Serial.println("Attempting to connect to ELM327..."); + + if (!myELM327.begin(ELM_PORT, DEBUG, TIMEOUT)) + { + Serial.println("Couldn't connect to OBD scanner"); + + if (HALT_ON_FAIL) + while (1); + } + + Serial.println("Connected to ELM327"); +} + + + + +void loop() +{ + switch (obd_state) + { + case ENG_RPM: + { + rpm = myELM327.rpm(); + + if (myELM327.nb_rx_state == ELM_SUCCESS) + { + Serial.print("rpm: "); + Serial.println(rpm); + obd_state = SPEED; + } + else if (myELM327.nb_rx_state != ELM_GETTING_MSG) + { + myELM327.printError(); + obd_state = SPEED; + } + + break; + } + + case SPEED: + { + mph = myELM327.mph(); + + if (myELM327.nb_rx_state == ELM_SUCCESS) + { + Serial.print("mph: "); + Serial.println(mph); + obd_state = ENG_RPM; + } + else if (myELM327.nb_rx_state != ELM_GETTING_MSG) + { + myELM327.printError(); + obd_state = ENG_RPM; + } + + break; + } + } +} diff --git a/library.properties b/library.properties index b7f67f4..e81e23b 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=ELMDuino -version=2.6.0 +version=3.4.1 author=PowerBroker2 maintainer=PowerBroker2 sentence=Arduino library to easily interface with the common OBDII scanner: ELM327 diff --git a/reference/ELM327DS.pdf b/reference/ELM327DS.pdf new file mode 100644 index 0000000..ca4962b Binary files /dev/null and b/reference/ELM327DS.pdf differ diff --git a/src/ELMduino.cpp b/src/ELMduino.cpp index 0e968c3..debaa56 100644 --- a/src/ELMduino.cpp +++ b/src/ELMduino.cpp @@ -1,10 +1,7 @@ #include "ELMduino.h" - - - /* - bool ELM327::begin(Stream &stream, const bool& debug, const uint16_t& timeout, const char& protocol, const uint16_t& payloadLen) + bool ELM327::begin(Stream &stream, const bool& debug, const uint16_t& timeout, const char& protocol, const uint16_t& payloadLen, const byte& dataTimeout) Description: ------------ @@ -12,42 +9,47 @@ Inputs: ------- - * Stream &stream - Pointer to Serial port connected to ELM327 + * Stream &stream - Reference to Serial port connected to ELM327 * bool debug - Specify whether or not to print debug statements to "Serial" * uint16_t timeout - Time in ms to wait for a query response * char protocol - Protocol ID to specify the ELM327 to communicate with the ECU over * uint16_t payloadLen - Maximum number of bytes expected to be returned by the ELM327 after a query - + * byte dataTimeout - Number of ms to wait after receiving data before the ELM327 will + return the data - see https://web.archive.org/web/20230729213500/https://www.elmelectronics.com/help/obd/tips/#UnderstandingOBD Return: ------- - * bool - Whether or not the ELM327 was propperly - initialized + * bool - Whether or not the ELM327 was properly initialized */ -bool ELM327::begin(Stream &stream, const bool& debug, const uint16_t& timeout, const char& protocol, const uint16_t& payloadLen) +bool ELM327::begin( Stream& stream, + const bool& debug, + const uint16_t& timeout, + const char& protocol, + const uint16_t& payloadLen, + const byte& dataTimeout) { - elm_port = &stream; - PAYLOAD_LEN = payloadLen; - debugMode = debug; - timeout_ms = timeout; + elm_port = &stream; + PAYLOAD_LEN = payloadLen; + debugMode = debug; + timeout_ms = timeout; + payload = (char *)malloc(PAYLOAD_LEN + 1); // allow for terminating '\0' - payload = (char*)malloc(PAYLOAD_LEN + 1); // allow for terminating '\0' + // test if serial port is connected + if (!elm_port) + return false; - // test if serial port is connected - if (!elm_port) - return false; + // try to connect + if (!initializeELM(protocol, dataTimeout)) + return false; - // try to connect - if (!initializeELM(protocol)) - return false; - - return true; + return true; } - - +ELM327::~ELM327() { + if (payload) free(payload); +} /* - bool ELM327::initializeELM(const char& protocol) + bool ELM327::initializeELM(const char& protocol, const byte& dataTimeout) Description: ------------ @@ -55,7 +57,9 @@ bool ELM327::begin(Stream &stream, const bool& debug, const uint16_t& timeout, c Inputs: ------- - * char protocol - Protocol ID to specify the ELM327 to communicate with the ECU over + * char protocol - Protocol ID to specify the ELM327 to communicate with the ECU over + * byte dataTimeout - Number of ms to wait after receiving data before the ELM327 will + return the data - see https://www.elmelectronics.com/help/obd/tips/#UnderstandingOBD Return: ------- @@ -80,68 +84,114 @@ bool ELM327::begin(Stream &stream, const bool& debug, const uint16_t& timeout, c * --> *user adjustable */ -bool ELM327::initializeELM(const char& protocol) -{ - char command[10] = { '\0' }; - connected = false; - - sendCommand(SET_ALL_TO_DEFAULTS); - delay(100); - - sendCommand(RESET_ALL); - delay(100); - - sendCommand(ECHO_OFF); - delay(100); - - sendCommand(PRINTING_SPACES_OFF); - delay(100); - - sendCommand(ALLOW_LONG_MESSAGES); - delay(100); - - // Set protocol - sprintf(command, TRY_PROT_H_AUTO_SEARCH, protocol); - - if (sendCommand(command) == ELM_SUCCESS) - { - if (strstr(payload, "OK") != NULL) - { - connected = true; - return connected; - } - } - - if (debugMode) - { - Serial.print(F("Setting protocol via ")); - Serial.print(TRY_PROT_H_AUTO_SEARCH); - Serial.print(F(" did not work - trying via ")); - Serial.println(SET_PROTOCOL_TO_H_SAVE); - } - - // Set protocol and save - sprintf(command, SET_PROTOCOL_TO_H_SAVE, protocol); - - if (sendCommand(command) == ELM_SUCCESS) - if (strstr(payload, "OK") != NULL) +bool ELM327::initializeELM(const char& protocol, + const byte& dataTimeout) +{ + char command[10] = {'\0'}; + connected = false; + + sendCommand_Blocking(SET_ALL_TO_DEFAULTS); + delay(100); + + sendCommand_Blocking(RESET_ALL); + delay(100); + + sendCommand_Blocking(ECHO_OFF); + delay(100); + + sendCommand_Blocking(PRINTING_SPACES_OFF); + delay(100); + + sendCommand_Blocking(ALLOW_LONG_MESSAGES); + delay(100); + + // // Set data timeout + snprintf(command, sizeof(command), SET_TIMEOUT_TO_H_X_4MS, dataTimeout / 4); + sendCommand_Blocking(command); + delay(100); + + // Automatic searching for protocol requires setting the protocol to AUTO and then + // sending an OBD command to initiate the protocol search. The OBD command "0100" + // requests a list of supported PIDs 0x00 - 0x20 and is guaranteed to work + if (protocol == '0') + { + // Tell the ELM327 to do an auto protocol search. If a valid protocol is found, it will be saved to memory. + // Some ELM clones may not have memory enabled and thus will perform the search every time. + snprintf(command, sizeof(command), SET_PROTOCOL_TO_AUTO_H_SAVE, protocol); + if (sendCommand_Blocking(command) == ELM_SUCCESS) + { + if (strstr(payload, RESPONSE_OK) != NULL) + { + // Protocol search can take a comparatively long time. Temporarily set + // the timeout value to 30 seconds, then restore the previous value. + uint16_t prevTimeout = timeout_ms; + timeout_ms = 30000; + + int8_t state = sendCommand_Blocking("0100"); + + if (state == ELM_SUCCESS) + { + timeout_ms = prevTimeout; + connected = true; + return connected; + } + else if (state == ELM_BUFFER_OVERFLOW) + { + while (elm_port->available()) + elm_port->read(); + } + + timeout_ms = prevTimeout; + } + } + } + else + { + // Set protocol + snprintf(command, sizeof(command), TRY_PROT_H_AUTO_SEARCH, protocol); + + if (sendCommand_Blocking(command) == ELM_SUCCESS) + { + if (strstr(payload, RESPONSE_OK) != NULL) + { + connected = true; + return connected; + } + } + } + + if (debugMode) + { + Serial.print(F("Setting protocol via ")); + Serial.print(TRY_PROT_H_AUTO_SEARCH); + Serial.print(F(" did not work - trying via ")); + Serial.println(SET_PROTOCOL_TO_H_SAVE); + } + + // Set protocol and save + snprintf(command, sizeof(command), SET_PROTOCOL_TO_H_SAVE, protocol); + + if (sendCommand_Blocking(command) == ELM_SUCCESS) + { + if (strstr(payload, RESPONSE_OK) != NULL) + { connected = true; + return connected; + } + } + + if (debugMode) + { + Serial.print(F("Setting protocol via ")); + Serial.print(SET_PROTOCOL_TO_H_SAVE); + Serial.println(F(" did not work")); + } - if (debugMode) - { - Serial.print(F("Setting protocol via ")); - Serial.print(SET_PROTOCOL_TO_H_SAVE); - Serial.println(F(" did not work")); - } - - return connected; + return connected; } - - - /* - void ELM327::formatQueryArray(uint8_t service, uint16_t pid) + void ELM327::formatQueryArray(uint8_t service, uint16_t pid, uint8_t num_responses) Description: ------------ @@ -151,65 +201,123 @@ bool ELM327::initializeELM(const char& protocol) ------- * uint16_t service - Service number of the queried PID * uint32_t pid - PID number of the queried PID - - Return: - ------- - * void -*/ -void ELM327::formatQueryArray(uint8_t service, uint16_t pid) -{ - if (debugMode) - { - Serial.print(F("Service: ")); - Serial.println(service); - Serial.print(F("PID: ")); - Serial.println(pid); - } - - query[0] = ((service >> 4) & 0xF) + '0'; - query[1] = (service & 0xF) + '0'; - - // determine PID length (standard queries have 16-bit PIDs, - // but some custom queries have PIDs with 32-bit values) - if (pid & 0xFF00) - { - if (debugMode) - Serial.println(F("Long query detected")); - - longQuery = true; - - query[2] = ((pid >> 12) & 0xF) + '0'; - query[3] = ((pid >> 8) & 0xF) + '0'; - query[4] = ((pid >> 4) & 0xF) + '0'; - query[5] = (pid & 0xF) + '0'; - - upper(query, 6); - } - else - { - if (debugMode) - Serial.println(F("Normal length query detected")); - - longQuery = false; - - query[2] = ((pid >> 4) & 0xF) + '0'; - query[3] = (pid & 0xF) + '0'; - query[4] = '\0'; - query[5] = '\0'; - - upper(query, 4); - } - - if (debugMode) - { - Serial.print(F("Query string: ")); - Serial.println(query); - } + * uint8_t num_responses - see function header for "queryPID()" + + Return: + ------- + * void +*/ +void ELM327::formatQueryArray(const uint8_t& service, + const uint16_t& pid, + const uint8_t& num_responses) +{ + if (debugMode) + { + Serial.print(F("Service: ")); + Serial.println(service); + Serial.print(F("PID: ")); + Serial.println(pid); + } + + isMode0x22Query = (service == 0x22 && pid <= 0xFF); // mode 0x22 responses always zero-pad the pid to 4 chars, even for a 2-char pid + + query[0] = ((service >> 4) & 0xF) + '0'; + query[1] = (service & 0xF) + '0'; + + // determine PID length (standard queries have 16-bit PIDs, + // but some custom queries have PIDs with 32-bit values) + if (pid & 0xFF00) + { + if (debugMode) + Serial.println(F("Long query detected")); + + longQuery = true; + + query[2] = ((pid >> 12) & 0xF) + '0'; + query[3] = ((pid >> 8) & 0xF) + '0'; + query[4] = ((pid >> 4) & 0xF) + '0'; + query[5] = (pid & 0xF) + '0'; + + if (specifyNumResponses) + { + if (num_responses > 0xF) + { + query[6] = ((num_responses >> 4) & 0xF) + '0'; + query[7] = (num_responses & 0xF) + '0'; + query[8] = '\0'; + + upper(query, 8); + } + else + { + query[6] = (num_responses & 0xF) + '0'; + query[7] = '\0'; + query[8] = '\0'; + + upper(query, 7); + } + } + else + { + query[6] = '\0'; + query[7] = '\0'; + query[8] = '\0'; + + upper(query, 6); + } + } + else + { + if (debugMode) + Serial.println(F("Normal length query detected")); + + longQuery = false; + + query[2] = ((pid >> 4) & 0xF) + '0'; + query[3] = (pid & 0xF) + '0'; + + if (specifyNumResponses) + { + if (num_responses > 0xF) + { + query[4] = ((num_responses >> 4) & 0xF) + '0'; + query[5] = (num_responses & 0xF) + '0'; + query[6] = '\0'; + query[7] = '\0'; + query[8] = '\0'; + + upper(query, 6); + } + else + { + query[4] = (num_responses & 0xF) + '0'; + query[5] = '\0'; + query[6] = '\0'; + query[7] = '\0'; + query[8] = '\0'; + + upper(query, 5); + } + } + else + { + query[4] = '\0'; + query[5] = '\0'; + query[6] = '\0'; + query[7] = '\0'; + query[8] = '\0'; + + upper(query, 4); + } + } + + if (debugMode) + { + Serial.print(F("Query string: ")); + Serial.println(query); + } } - - - /* void ELM327::upper(char string[], uint8_t buflen) @@ -227,20 +335,18 @@ void ELM327::formatQueryArray(uint8_t service, uint16_t pid) ------- * void */ -void ELM327::upper(char string[], uint8_t buflen) +void ELM327::upper(char string[], + uint8_t buflen) { - for (uint8_t i = 0; i < buflen; i++) - { - if (string[i] > 'Z') - string[i] -= 32; - else if ((string[i] > '9') && (string[i] < 'A')) - string[i] += 7; - } + for (uint8_t i = 0; i < buflen; i++) + { + if (string[i] > 'Z') + string[i] -= 32; + else if ((string[i] > '9') && (string[i] < 'A')) + string[i] += 7; + } } - - - /* bool ELM327::timeout() @@ -258,15 +364,12 @@ void ELM327::upper(char string[], uint8_t buflen) */ bool ELM327::timeout() { - currentTime = millis(); - if ((currentTime - previousTime) >= timeout_ms) - return true; - return false; + currentTime = millis(); + if ((currentTime - previousTime) >= timeout_ms) + return true; + return false; } - - - /* uint8_t ELM327::ctoi(uint8_t value) @@ -284,15 +387,12 @@ bool ELM327::timeout() */ uint8_t ELM327::ctoi(uint8_t value) { - if (value >= 'A') - return value - 'A' + 10; - else - return value - '0'; + if (value >= 'A') + return value - 'A' + 10; + else + return value - '0'; } - - - /* int8_t ELM327::nextIndex(char const *str, char const *target, @@ -308,7 +408,7 @@ uint8_t ELM327::ctoi(uint8_t value) * char const *str - string to search target within * char const *target - String to search for in str * uint8_t numOccur - Which instance of target in str - + Return: ------- * int8_t - First char index of numOccur'th @@ -317,101 +417,226 @@ uint8_t ELM327::ctoi(uint8_t value) */ int8_t ELM327::nextIndex(char const *str, char const *target, - uint8_t numOccur=1) + uint8_t numOccur) { - char const *p = str; - char const *r = str; - uint8_t count; + char const *p = str; + char const *r = str; + uint8_t count; - for (count = 0; ; ++count) - { - p = strstr(p, target); + for (count = 0;; ++count) + { + p = strstr(p, target); - if (count == (numOccur - 1)) - break; + if (count == (numOccur - 1)) + break; - if (!p) - break; + if (!p) + break; - p++; - } + p++; + } - if (!p) - return -1; + if (!p) + return -1; - return p - r; + return p - r; } +/* + void ELM327::removeChar(char *from, + char const *remove) + Description: + ------------ + * Removes all instances of each char in string "remove" from the string "from" + Inputs: + ------- + * char *from - String to remove target(s) from + * char const *remove - Chars to find/remove -float ELM327::conditionResponse(const uint64_t& response, const uint8_t& numExpectedBytes, const float& scaleFactor, const float& bias) + Return: + ------- + * void +*/ +void ELM327::removeChar(char *from, const char *remove) { - return ((response >> (((numPayChars / 2) - numExpectedBytes) * 8)) * scaleFactor) + bias; + size_t i = 0, j = 0; + while (from[i]) { + if (!strchr(remove, from[i])) + from[j++] = from[i]; + i++; + } + from[j] = '\0'; } - - - - /* - void ELM327::flushInputBuff() + double ELM327::conditionResponse(const uint8_t &numExpectedBytes, const float &scaleFactor, const float &bias) Description: ------------ - * Flushes input serial buffer + * Converts the ELM327's response into its correct, numerical value. Returns 0 if numExpectedBytes > numPayChars Inputs: ------- - * void + * uint64_t response - ELM327's response + * uint8_t numExpectedBytes - Number of valid bytes from the response to process + * double scaleFactor - Amount to scale the response by + * float bias - Amount to bias the response by Return: ------- - * void + * double - Converted numerical value */ -void ELM327::flushInputBuff() +double ELM327::conditionResponse(const uint8_t& numExpectedBytes, + const double& scaleFactor, + const double& bias) { - if (debugMode) - Serial.println(F("Clearing input serial buffer")); + uint8_t numExpectedPayChars = numExpectedBytes * 2; + uint8_t payCharDiff = numPayChars - numExpectedPayChars; + + if (numExpectedBytes > 8) + { + if (debugMode) + Serial.println(F("WARNING: Number of expected response bytes is greater than 8 - returning 0")); + + return 0; + } + + if (numPayChars < numExpectedPayChars) + { + if (debugMode) + Serial.println(F("WARNING: Number of payload chars is less than the number of expected response chars returned by ELM327 - returning 0")); + + return 0; + } + else if (numPayChars & 0x1) + { + if (debugMode) + Serial.println(F("WARNING: Number of payload chars returned by ELM327 is an odd value - returning 0")); + + return 0; + } + else if (numExpectedPayChars == numPayChars) + { + if (scaleFactor == 1 && bias == 0) // No scale/bias needed + return response; + else + return (response * scaleFactor) + bias; + } + + // If there were more payload bytes returned than we expected, test the first and last bytes in the + // returned payload and see which gives us a higher value. Sometimes ELM327's return leading zeros + // and others return trailing zeros. The following approach gives us the best chance at determining + // where the real data is. Note that if the payload returns BOTH leading and trailing zeros, this + // will not give accurate results! + + if (debugMode) + Serial.println(F("Looking for lagging zeros")); + + uint16_t numExpectedBits = numExpectedBytes * 8; + uint64_t laggingZerosMask = 0; + + for (uint16_t i = 0; i < numExpectedBits; i++) + laggingZerosMask |= (1 << i); + + if (!(laggingZerosMask & response)) // Detect all lagging zeros in `response` + { + if (debugMode) + Serial.println(F("Lagging zeros found")); - while (elm_port->available()) - elm_port->read(); + if (scaleFactor == 1 && bias == 0) // No scale/bias needed + return (response >> (4 * payCharDiff)); + else + return ((response >> (4 * payCharDiff)) * scaleFactor) + bias; + } + else + { + if (debugMode) + Serial.println(F("Lagging zeros not found - assuming leading zeros")); + + if (scaleFactor == 1 && bias == 0) // No scale/bias needed + return response; + else + return (response * scaleFactor) + bias; + } } +/* + double ELM327::conditionResponse(double (*func)()) + + Description: + ------------ + * Provides a means to pass in a user-defined function to process the response. Used for PIDs that + don't use the common scaleFactor + Bias formula to calculate the value from the response data. Also + useful for processing OEM custom PIDs which are too numerous and varied to encode in the lib. + Inputs: + ------- + * (*func)() - pointer to function to do calculate response value + + Return: + ------- + * double - Converted numerical value +*/ +double ELM327::conditionResponse(double (*func)()) { + return func(); +} /* - bool ELM327::queryPID(uint8_t service, - uint16_t PID, - float &value) + void ELM327::flushInputBuff() Description: ------------ - * Queries ELM327 for a specific type of vehicle telemetry data + * Flushes input serial buffer Inputs: ------- - * uint8_t service - Service number - * uint8_t PID - PID number + * void Return: ------- - * bool - Whether or not the query was submitted successfully + * void */ -bool ELM327::queryPID(uint8_t service, - uint16_t pid) +void ELM327::flushInputBuff() { - formatQueryArray(service, pid); - sendCommand(query); - - return connected; + if (debugMode) + Serial.println(F("Clearing input serial buffer")); + + while (elm_port->available()) + elm_port->read(); } +/* + void ELM327::queryPID(const uint8_t& service, const uint16_t& pid, const uint8_t& num_responses) + + Description: + ------------ + * Create a PID query command string and send the command + Inputs: + ------- + * uint8_t service - The diagnostic service ID. 01 is "Show current data" + * uint16_t pid - The Parameter ID (PID) from the service + * uint8_t num_responses - Number of lines of data to receive - see ELM datasheet "Talking to the vehicle". + This can speed up retrieval of information if you know how many responses will be sent. + Basically the OBD scanner will not wait for more responses if it does not need to go through + final timeout. Also prevents OBD scanners from sending mulitple of the same response. + Return: + ------- + * void +*/ +void ELM327::queryPID(const uint8_t& service, + const uint16_t& pid, + const uint8_t& num_responses) +{ + formatQueryArray(service, pid, num_responses); + sendCommand(query); +} /* - bool ELM327::queryPID(char queryStr[]) + void ELM327::queryPID(char queryStr[]) Description: ------------ @@ -423,22 +648,252 @@ bool ELM327::queryPID(uint8_t service, Return: ------- - * bool - Whether or not the query was submitted successfully + * void */ -bool ELM327::queryPID(char queryStr[]) +void ELM327::queryPID(char queryStr[]) { - if (strlen(queryStr) <= 4) - longQuery = false; - else - longQuery = true; + if (strlen(queryStr) <= 4) + longQuery = false; + else + longQuery = true; - sendCommand(queryStr); - - return connected; + sendCommand(queryStr); } +/* + double ELM327::processPID(const uint8_t& service, const uint16_t& pid, const uint8_t& num_responses, const uint8_t& numExpectedBytes, const float& scaleFactor, const float& bias) + + Description: + ------------ + * Queries ELM327 for a specific type of vehicle telemetry data + Inputs: + ------- + * uint8_t service - The diagnostic service ID. 01 is "Show current data" + * uint16_t pid - The Parameter ID (PID) from the service + * uint8_t num_responses - Number of lines of data to receive - see ELM datasheet "Talking to the vehicle". + This can speed up retrieval of information if you know how many responses will be sent. + Basically the OBD scanner will not wait for more responses if it does not need to go through + final timeout. Also prevents OBD scanners from sending mulitple of the same response. + * uint8_t numExpectedBytes - Number of valid bytes from the response to process + * float scaleFactor - Amount to scale the response by + * float bias - Amount to bias the response by + + Return: + ------- + * double - The PID value if successfully received, else 0.0 +*/ +double ELM327::processPID(const uint8_t& service, + const uint16_t& pid, + const uint8_t& num_responses, + const uint8_t& numExpectedBytes, + const double& scaleFactor, + const float& bias) +{ + if (nb_query_state == SEND_COMMAND) + { + queryPID(service, pid, num_responses); + nb_query_state = WAITING_RESP; + } + else if (nb_query_state == WAITING_RESP) + { + get_response(); + if (nb_rx_state == ELM_SUCCESS) + { + nb_query_state = SEND_COMMAND; // Reset the query state machine for next command + findResponse(); + + /* This data manipulation seems duplicative of the responseByte_0, responseByte_1, etc vars and it is. + The duplcation is deliberate to provide a clear way for the calculator functions to access the relevant + data bytes from the response in the format they are commonly expressed in and without breaking backward + compatability with existing code that may be using the responseByte_n vars. + + In addition, we need to place the response values into static vars that can be accessed by the (static) + calculator functions. A future (breaking!) change could be made to eliminate this duplication. + */ + uint8_t responseBits = numExpectedBytes * 8; + uint8_t extractedBytes[8] = {0}; // Store extracted bytes + + // Extract bytes only if shift is non-negative + for (int i = 0; i < numExpectedBytes; i++) + { + int shiftAmount = responseBits - (8 * (i + 1)); // Compute shift amount + if (shiftAmount >= 0) { // Ensure valid shift + extractedBytes[i] = (response >> shiftAmount) & 0xFF; // Extract byte + } + } + + // Assign extracted values to response_A, response_B, ..., response_H safely + response_A = extractedBytes[0]; + response_B = extractedBytes[1]; + response_C = extractedBytes[2]; + response_D = extractedBytes[3]; + response_E = extractedBytes[4]; + response_F = extractedBytes[5]; + response_G = extractedBytes[6]; + response_H = extractedBytes[7]; + + double (*calculator)() = selectCalculator(pid); + + if (nullptr == calculator) { + //Use the default scaleFactor + Bias calculation + return conditionResponse(numExpectedBytes, scaleFactor, bias); + } + else { + return conditionResponse(calculator); + } + } + else if (nb_rx_state != ELM_GETTING_MSG) + nb_query_state = SEND_COMMAND; // Error or timeout, so reset the query state machine for next command + } + return 0.0; +} +/* + double ELM327::selectCalculator(uint16_t pid))() + + Description: + ------------ + * Selects the appropriate calculation function for a given PID. + + Inputs: + ------- + * uint16_t pid - The Parameter ID (PID) from the service + + Return: + ------- + * double (*func()) - Pointer to a function to be used to calculate the value for this PID. + Returns nullptr if the PID is calculated using the default scaleFactor + Bias formula as + implemented in conditionResponse(). (Maintained for backward compatibility) +*/ +double (*ELM327::selectCalculator(uint16_t pid))() +{ + switch (pid) + { + case ENGINE_LOAD: + case ENGINE_COOLANT_TEMP: + case SHORT_TERM_FUEL_TRIM_BANK_1: + case LONG_TERM_FUEL_TRIM_BANK_1: + case SHORT_TERM_FUEL_TRIM_BANK_2: + case LONG_TERM_FUEL_TRIM_BANK_2: + case FUEL_PRESSURE: + case INTAKE_MANIFOLD_ABS_PRESSURE: + case VEHICLE_SPEED: + case TIMING_ADVANCE: + case INTAKE_AIR_TEMP: + case THROTTLE_POSITION: + case COMMANDED_EGR: + case EGR_ERROR: + case COMMANDED_EVAPORATIVE_PURGE: + case FUEL_TANK_LEVEL_INPUT: + case WARM_UPS_SINCE_CODES_CLEARED: + case ABS_BAROMETRIC_PRESSURE: + case RELATIVE_THROTTLE_POSITION: + case AMBIENT_AIR_TEMP: + case ABS_THROTTLE_POSITION_B: + case ABS_THROTTLE_POSITION_C: + case ABS_THROTTLE_POSITION_D: + case ABS_THROTTLE_POSITION_E: + case ABS_THROTTLE_POSITION_F: + case COMMANDED_THROTTLE_ACTUATOR: + case ETHANOL_FUEL_PERCENT: + case RELATIVE_ACCELERATOR_PEDAL_POS: + case HYBRID_BATTERY_REMAINING_LIFE: + case ENGINE_OIL_TEMP: + case DEMANDED_ENGINE_PERCENT_TORQUE: + case ACTUAL_ENGINE_TORQUE: + return nullptr; + + case ENGINE_RPM: + return calculator_0C; + + case MAF_FLOW_RATE: + return calculator_10; + + case OXYGEN_SENSOR_1_A: + case OXYGEN_SENSOR_2_A: + case OXYGEN_SENSOR_3_A: + case OXYGEN_SENSOR_4_A: + case OXYGEN_SENSOR_5_A: + case OXYGEN_SENSOR_6_A: + case OXYGEN_SENSOR_7_A: + case OXYGEN_SENSOR_8_A: + case OXYGEN_SENSOR_1_B: + case OXYGEN_SENSOR_2_B: + case OXYGEN_SENSOR_3_B: + case OXYGEN_SENSOR_4_B: + case OXYGEN_SENSOR_6_B: + case OXYGEN_SENSOR_7_B: + case OXYGEN_SENSOR_8_B: + case OXYGEN_SENSOR_1_C: + case OXYGEN_SENSOR_2_C: + case OXYGEN_SENSOR_3_C: + case OXYGEN_SENSOR_4_C: + case OXYGEN_SENSOR_5_C: + case OXYGEN_SENSOR_6_C: + case OXYGEN_SENSOR_7_C: + case OXYGEN_SENSOR_8_C: + return calculator_14; + + case RUN_TIME_SINCE_ENGINE_START: + case DISTANCE_TRAVELED_WITH_MIL_ON: + case DIST_TRAV_SINCE_CODES_CLEARED: + case TIME_RUN_WITH_MIL_ON: + case TIME_SINCE_CODES_CLEARED: + case ENGINE_REFERENCE_TORQUE: + return calculator_1F; + + case FUEL_RAIL_PRESSURE: + return calculator_22; + + case FUEL_RAIL_GUAGE_PRESSURE: + case FUEL_RAIL_ABS_PRESSURE: + return calculator_23; + + case EVAP_SYSTEM_VAPOR_PRESSURE: + return calculator_32; + + case CATALYST_TEMP_BANK_1_SENSOR_1: + case CATALYST_TEMP_BANK_2_SENSOR_1: + case CATALYST_TEMP_BANK_1_SENSOR_2: + case CATALYST_TEMP_BANK_2_SENSOR_2: + return calculator_3C; + + case CONTROL_MODULE_VOLTAGE: + return calculator_42; + + case ABS_LOAD_VALUE: + return calculator_43; + + case FUEL_AIR_COMMANDED_EQUIV_RATIO: + return calculator_44; + + case MAX_VALUES_EQUIV_V_I_PRESSURE: + return calculator_4F; + + case MAX_MAF_RATE: + return calculator_50; + + case ABS_EVAP_SYS_VAPOR_PRESSURE: + return calculator_53; + + case SHORT_TERM_SEC_OXY_SENS_TRIM_1_3: + case LONG_TERM_SEC_OXY_SENS_TRIM_1_3: + case SHORT_TERM_SEC_OXY_SENS_TRIM_2_4: + case LONG_TERM_SEC_OXY_SENS_TRIM_2_4: + return calculator_55; + + case FUEL_INJECTION_TIMING: + return calculator_5D; + + case ENGINE_FUEL_RATE: + return calculator_5E; + + default: + return nullptr; + + } +} /* uint32_t ELM327::supportedPIDs_1_20() @@ -457,15 +912,9 @@ bool ELM327::queryPID(char queryStr[]) */ uint32_t ELM327::supportedPIDs_1_20() { - if (queryPID(SERVICE_01, SUPPORTED_PIDS_1_20)) - return conditionResponse(findResponse(), 4); - - return ELM_GENERAL_ERROR; + return (uint32_t)processPID(SERVICE_01, SUPPORTED_PIDS_1_20, 1, 4); } - - - /* uint32_t ELM327::monitorStatus() @@ -485,15 +934,9 @@ uint32_t ELM327::supportedPIDs_1_20() */ uint32_t ELM327::monitorStatus() { - if (queryPID(SERVICE_01, MONITOR_STATUS_SINCE_DTC_CLEARED)) - return conditionResponse(findResponse(), 4); - - return ELM_GENERAL_ERROR; + return (uint32_t)processPID(SERVICE_01, MONITOR_STATUS_SINCE_DTC_CLEARED, 1, 4); } - - - /* uint16_t ELM327::freezeDTC() @@ -511,15 +954,9 @@ uint32_t ELM327::monitorStatus() */ uint16_t ELM327::freezeDTC() { - if (queryPID(SERVICE_01, FREEZE_DTC)) - return conditionResponse(findResponse(), 2); - - return ELM_GENERAL_ERROR; + return (uint16_t)processPID(SERVICE_01, FREEZE_DTC, 1, 2); } - - - /* uint16_t ELM327::fuelSystemStatus() @@ -537,15 +974,9 @@ uint16_t ELM327::freezeDTC() */ uint16_t ELM327::fuelSystemStatus() { - if (queryPID(SERVICE_01, FUEL_SYSTEM_STATUS)) - return conditionResponse(findResponse(), 2); - - return ELM_GENERAL_ERROR; + return (uint16_t)processPID(SERVICE_01, FUEL_SYSTEM_STATUS, 1, 2); } - - - /* float ELM327::engineLoad() @@ -563,15 +994,9 @@ uint16_t ELM327::fuelSystemStatus() */ float ELM327::engineLoad() { - if (queryPID(SERVICE_01, ENGINE_LOAD)) - return conditionResponse(findResponse(), 1, 100.0 / 255.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, ENGINE_LOAD, 1, 1, 100.0 / 255.0); } - - - /* float ELM327::engineCoolantTemp() @@ -589,15 +1014,9 @@ float ELM327::engineLoad() */ float ELM327::engineCoolantTemp() { - if (queryPID(SERVICE_01, ENGINE_COOLANT_TEMP)) - return conditionResponse(findResponse(), 1, 1, -40.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, ENGINE_COOLANT_TEMP, 1, 1, 1, -40.0); } - - - /* float ELM327::shortTermFuelTrimBank_1() @@ -615,15 +1034,9 @@ float ELM327::engineCoolantTemp() */ float ELM327::shortTermFuelTrimBank_1() { - if (queryPID(SERVICE_01, SHORT_TERM_FUEL_TRIM_BANK_1)) - return conditionResponse(findResponse(), 1, 100.0 / 128.0, -100.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, SHORT_TERM_FUEL_TRIM_BANK_1, 1, 1, 100.0 / 128.0, -100.0); } - - - /* float ELM327::longTermFuelTrimBank_1() @@ -641,15 +1054,9 @@ float ELM327::shortTermFuelTrimBank_1() */ float ELM327::longTermFuelTrimBank_1() { - if (queryPID(SERVICE_01, LONG_TERM_FUEL_TRIM_BANK_1)) - return conditionResponse(findResponse(), 1, 100.0 / 128.0, -100.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, LONG_TERM_FUEL_TRIM_BANK_1, 1, 1, 100.0 / 128.0, -100.0); } - - - /* float ELM327::shortTermFuelTrimBank_2() @@ -667,15 +1074,9 @@ float ELM327::longTermFuelTrimBank_1() */ float ELM327::shortTermFuelTrimBank_2() { - if (queryPID(SERVICE_01, SHORT_TERM_FUEL_TRIM_BANK_2)) - return conditionResponse(findResponse(), 1, 100.0 / 128.0, -100.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, SHORT_TERM_FUEL_TRIM_BANK_2, 1, 1, 100.0 / 128.0, -100.0); } - - - /* float ELM327::longTermFuelTrimBank_2() @@ -693,15 +1094,9 @@ float ELM327::shortTermFuelTrimBank_2() */ float ELM327::longTermFuelTrimBank_2() { - if (queryPID(SERVICE_01, LONG_TERM_FUEL_TRIM_BANK_2)) - return conditionResponse(findResponse(), 1, 100.0 / 128.0, -100.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, LONG_TERM_FUEL_TRIM_BANK_2, 1, 1, 100.0 / 128.0, -100.0); } - - - /* float ELM327::fuelPressure() @@ -719,15 +1114,9 @@ float ELM327::longTermFuelTrimBank_2() */ float ELM327::fuelPressure() { - if (queryPID(SERVICE_01, FUEL_PRESSURE)) - return conditionResponse(findResponse(), 1, 3.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, FUEL_PRESSURE, 1, 1, 3.0); } - - - /* uint8_t ELM327::manifoldPressure() @@ -745,15 +1134,9 @@ float ELM327::fuelPressure() */ uint8_t ELM327::manifoldPressure() { - if (queryPID(SERVICE_01, INTAKE_MANIFOLD_ABS_PRESSURE)) - return conditionResponse(findResponse(), 1); - - return ELM_GENERAL_ERROR; + return (uint8_t)processPID(SERVICE_01, INTAKE_MANIFOLD_ABS_PRESSURE, 1, 1); } - - - /* float ELM327::rpm() @@ -771,15 +1154,9 @@ uint8_t ELM327::manifoldPressure() */ float ELM327::rpm() { - if (queryPID(SERVICE_01, ENGINE_RPM)) - return conditionResponse(findResponse(), 2, 1.0 / 4.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, ENGINE_RPM, 1, 2, 1.0 / 4.0); } - - - /* int32_t ELM327::kph() @@ -797,15 +1174,9 @@ float ELM327::rpm() */ int32_t ELM327::kph() { - if (queryPID(SERVICE_01, VEHICLE_SPEED)) - return conditionResponse(findResponse(), 1); - - return ELM_GENERAL_ERROR; + return (int32_t)processPID(SERVICE_01, VEHICLE_SPEED, 1, 1); } - - - /* float ELM327::mph() @@ -823,17 +1194,9 @@ int32_t ELM327::kph() */ float ELM327::mph() { - float mph = kph(); - - if (status == ELM_SUCCESS) - return mph * KPH_MPH_CONVERT; - - return ELM_GENERAL_ERROR; + return kph() * KPH_MPH_CONVERT; } - - - /* float ELM327::timingAdvance() @@ -851,15 +1214,9 @@ float ELM327::mph() */ float ELM327::timingAdvance() { - if (queryPID(SERVICE_01, TIMING_ADVANCE)) - return conditionResponse(findResponse(), 1, 1.0 / 2.0, -64.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, TIMING_ADVANCE, 1, 1, 1.0 / 2.0, -64.0); } - - - /* float ELM327::intakeAirTemp() @@ -877,15 +1234,9 @@ float ELM327::timingAdvance() */ float ELM327::intakeAirTemp() { - if (queryPID(SERVICE_01, INTAKE_AIR_TEMP)) - return conditionResponse(findResponse(), 1, 1, -40.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, INTAKE_AIR_TEMP, 1, 1, 1, -40.0); } - - - /* float ELM327::mafRate() @@ -903,15 +1254,9 @@ float ELM327::intakeAirTemp() */ float ELM327::mafRate() { - if (queryPID(SERVICE_01, MAF_FLOW_RATE)) - return conditionResponse(findResponse(), 2, 1.0 / 100.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, MAF_FLOW_RATE, 1, 2, 1.0 / 100.0); } - - - /* float ELM327::throttle() @@ -929,15 +1274,9 @@ float ELM327::mafRate() */ float ELM327::throttle() { - if (queryPID(SERVICE_01, THROTTLE_POSITION)) - return conditionResponse(findResponse(), 1, 100.0 / 255.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, THROTTLE_POSITION, 1, 1, 100.0 / 255.0); } - - - /* uint8_t ELM327::commandedSecAirStatus() @@ -955,15 +1294,9 @@ float ELM327::throttle() */ uint8_t ELM327::commandedSecAirStatus() { - if (queryPID(SERVICE_01, COMMANDED_SECONDARY_AIR_STATUS)) - return conditionResponse(findResponse(), 1); - - return ELM_GENERAL_ERROR; + return (uint8_t)processPID(SERVICE_01, COMMANDED_SECONDARY_AIR_STATUS, 1, 1); } - - - /* uint8_t ELM327::oxygenSensorsPresent_2banks() @@ -981,15 +1314,9 @@ uint8_t ELM327::commandedSecAirStatus() */ uint8_t ELM327::oxygenSensorsPresent_2banks() { - if (queryPID(SERVICE_01, OXYGEN_SENSORS_PRESENT_2_BANKS)) - return conditionResponse(findResponse(), 1); - - return ELM_GENERAL_ERROR; + return (uint8_t)processPID(SERVICE_01, OXYGEN_SENSORS_PRESENT_2_BANKS, 1, 1); } - - - /* uint8_t ELM327::obdStandards() @@ -1007,15 +1334,9 @@ uint8_t ELM327::oxygenSensorsPresent_2banks() */ uint8_t ELM327::obdStandards() { - if (queryPID(SERVICE_01, OBD_STANDARDS)) - return conditionResponse(findResponse(), 1); - - return ELM_GENERAL_ERROR; + return (uint8_t)processPID(SERVICE_01, OBD_STANDARDS, 1, 1); } - - - /* uint8_t ELM327::oxygenSensorsPresent_4banks() @@ -1033,22 +1354,16 @@ uint8_t ELM327::obdStandards() */ uint8_t ELM327::oxygenSensorsPresent_4banks() { - if (queryPID(SERVICE_01, OXYGEN_SENSORS_PRESENT_4_BANKS)) - return conditionResponse(findResponse(), 1); - - return ELM_GENERAL_ERROR; + return (uint8_t)processPID(SERVICE_01, OXYGEN_SENSORS_PRESENT_4_BANKS, 1, 1); } - - - /* bool ELM327::auxInputStatus() Description: ------------ * Find Power Take Off (PTO) status - + Inputs: ------- * void @@ -1059,15 +1374,9 @@ uint8_t ELM327::oxygenSensorsPresent_4banks() */ bool ELM327::auxInputStatus() { - if (queryPID(SERVICE_01, AUX_INPUT_STATUS)) - return conditionResponse(findResponse(), 1); - - return ELM_GENERAL_ERROR; + return (bool)processPID(SERVICE_01, AUX_INPUT_STATUS, 1, 1); } - - - /* uint16_t ELM327::runTime() @@ -1085,15 +1394,9 @@ bool ELM327::auxInputStatus() */ uint16_t ELM327::runTime() { - if (queryPID(SERVICE_01, RUN_TIME_SINCE_ENGINE_START)) - return conditionResponse(findResponse(), 2); - - return ELM_GENERAL_ERROR; + return (uint16_t)processPID(SERVICE_01, RUN_TIME_SINCE_ENGINE_START, 1, 2); } - - - /* uint32_t ELM327::supportedPIDs_21_40() @@ -1111,15 +1414,9 @@ uint16_t ELM327::runTime() */ uint32_t ELM327::supportedPIDs_21_40() { - if (queryPID(SERVICE_01, SUPPORTED_PIDS_21_40)) - return conditionResponse(findResponse(), 4); - - return ELM_GENERAL_ERROR; + return (uint32_t)processPID(SERVICE_01, SUPPORTED_PIDS_21_40, 1, 4); } - - - /* uint16_t ELM327::distTravelWithMIL() @@ -1137,15 +1434,9 @@ uint32_t ELM327::supportedPIDs_21_40() */ uint16_t ELM327::distTravelWithMIL() { - if (queryPID(SERVICE_01, DISTANCE_TRAVELED_WITH_MIL_ON)) - return conditionResponse(findResponse(), 2); - - return ELM_GENERAL_ERROR; + return (uint16_t)processPID(SERVICE_01, DISTANCE_TRAVELED_WITH_MIL_ON, 1, 2); } - - - /* float ELM327::fuelRailPressure() @@ -1163,15 +1454,9 @@ uint16_t ELM327::distTravelWithMIL() */ float ELM327::fuelRailPressure() { - if (queryPID(SERVICE_01, FUEL_RAIL_PRESSURE)) - return conditionResponse(findResponse(), 2, 0.079); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, FUEL_RAIL_PRESSURE, 1, 2, 0.079); } - - - /* float ELM327::fuelRailGuagePressure() @@ -1189,15 +1474,9 @@ float ELM327::fuelRailPressure() */ float ELM327::fuelRailGuagePressure() { - if (queryPID(SERVICE_01, FUEL_RAIL_GUAGE_PRESSURE)) - return conditionResponse(findResponse(), 2, 10.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, FUEL_RAIL_GUAGE_PRESSURE, 1, 2, 10.0); } - - - /* float ELM327::commandedEGR() @@ -1215,15 +1494,9 @@ float ELM327::fuelRailGuagePressure() */ float ELM327::commandedEGR() { - if (queryPID(SERVICE_01, COMMANDED_EGR)) - return conditionResponse(findResponse(), 1, 100.0 / 255.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, COMMANDED_EGR, 1, 1, 100.0 / 255.0); } - - - /* float ELM327::egrError() @@ -1241,15 +1514,9 @@ float ELM327::commandedEGR() */ float ELM327::egrError() { - if (queryPID(SERVICE_01, EGR_ERROR)) - return conditionResponse(findResponse(), 1, 100.0 / 128.0, -100); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, EGR_ERROR, 1, 1, 100.0 / 128.0, -100); } - - - /* float ELM327::commandedEvapPurge() @@ -1267,15 +1534,9 @@ float ELM327::egrError() */ float ELM327::commandedEvapPurge() { - if (queryPID(SERVICE_01, COMMANDED_EVAPORATIVE_PURGE)) - return conditionResponse(findResponse(), 1, 100.0 / 255.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, COMMANDED_EVAPORATIVE_PURGE, 1, 1, 100.0 / 255.0); } - - - /* float ELM327::fuelLevel() @@ -1293,15 +1554,9 @@ float ELM327::commandedEvapPurge() */ float ELM327::fuelLevel() { - if (queryPID(SERVICE_01, FUEL_TANK_LEVEL_INPUT)) - return conditionResponse(findResponse(), 1, 100.0 / 255.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, FUEL_TANK_LEVEL_INPUT, 1, 1, 100.0 / 255.0); } - - - /* uint8_t ELM327::warmUpsSinceCodesCleared() @@ -1319,15 +1574,9 @@ float ELM327::fuelLevel() */ uint8_t ELM327::warmUpsSinceCodesCleared() { - if (queryPID(SERVICE_01, WARM_UPS_SINCE_CODES_CLEARED)) - return conditionResponse(findResponse(), 1); - - return ELM_GENERAL_ERROR; + return (uint8_t)processPID(SERVICE_01, WARM_UPS_SINCE_CODES_CLEARED, 1, 1); } - - - /* uint16_t ELM327::distSinceCodesCleared() @@ -1345,15 +1594,9 @@ uint8_t ELM327::warmUpsSinceCodesCleared() */ uint16_t ELM327::distSinceCodesCleared() { - if (queryPID(SERVICE_01, DIST_TRAV_SINCE_CODES_CLEARED)) - return conditionResponse(findResponse(), 2); - - return ELM_GENERAL_ERROR; + return (uint16_t)processPID(SERVICE_01, DIST_TRAV_SINCE_CODES_CLEARED, 1, 2); } - - - /* float ELM327::evapSysVapPressure() @@ -1371,15 +1614,9 @@ uint16_t ELM327::distSinceCodesCleared() */ float ELM327::evapSysVapPressure() { - if (queryPID(SERVICE_01, EVAP_SYSTEM_VAPOR_PRESSURE)) - return conditionResponse(findResponse(), 2, 1.0 / 4.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, EVAP_SYSTEM_VAPOR_PRESSURE, 1, 2, 1.0 / 4.0); } - - - /* uint8_t ELM327::absBaroPressure() @@ -1397,15 +1634,9 @@ float ELM327::evapSysVapPressure() */ uint8_t ELM327::absBaroPressure() { - if (queryPID(SERVICE_01, ABS_BAROMETRIC_PRESSURE)) - return conditionResponse(findResponse(), 1); - - return ELM_GENERAL_ERROR; + return (uint8_t)processPID(SERVICE_01, ABS_BAROMETRIC_PRESSURE, 1, 1); } - - - /* float ELM327::catTempB1S1() @@ -1423,15 +1654,9 @@ uint8_t ELM327::absBaroPressure() */ float ELM327::catTempB1S1() { - if (queryPID(SERVICE_01, CATALYST_TEMP_BANK_1_SENSOR_1)) - return conditionResponse(findResponse(), 2, 1.0 / 10.0, -40.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, CATALYST_TEMP_BANK_1_SENSOR_1, 1, 2, 1.0 / 10.0, -40.0); } - - - /* float ELM327::catTempB2S1() @@ -1449,15 +1674,9 @@ float ELM327::catTempB1S1() */ float ELM327::catTempB2S1() { - if (queryPID(SERVICE_01, CATALYST_TEMP_BANK_2_SENSOR_1)) - return conditionResponse(findResponse(), 2, 1.0 / 10.0, -40.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, CATALYST_TEMP_BANK_2_SENSOR_1, 1, 2, 1.0 / 10.0, -40.0); } - - - /* float ELM327::catTempB1S2() @@ -1475,15 +1694,9 @@ float ELM327::catTempB2S1() */ float ELM327::catTempB1S2() { - if (queryPID(SERVICE_01, CATALYST_TEMP_BANK_1_SENSOR_2)) - return conditionResponse(findResponse(), 2, 1.0 / 10.0, -40.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, CATALYST_TEMP_BANK_1_SENSOR_2, 1, 2, 1.0 / 10.0, -40.0); } - - - /* float ELM327::catTempB2S2() @@ -1501,15 +1714,9 @@ float ELM327::catTempB1S2() */ float ELM327::catTempB2S2() { - if (queryPID(SERVICE_01, CATALYST_TEMP_BANK_2_SENSOR_2)) - return conditionResponse(findResponse(), 2, 1.0 / 10.0, -40.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, CATALYST_TEMP_BANK_2_SENSOR_2, 1, 2, 1.0 / 10.0, -40.0); } - - - /* uint32_t ELM327::supportedPIDs_41_60() @@ -1527,15 +1734,9 @@ float ELM327::catTempB2S2() */ uint32_t ELM327::supportedPIDs_41_60() { - if (queryPID(SERVICE_01, SUPPORTED_PIDS_41_60)) - return conditionResponse(findResponse(), 4); - - return ELM_GENERAL_ERROR; + return (uint32_t)processPID(SERVICE_01, SUPPORTED_PIDS_41_60, 1, 4); } - - - /* uint32_t ELM327::monitorDriveCycleStatus() @@ -1553,15 +1754,9 @@ uint32_t ELM327::supportedPIDs_41_60() */ uint32_t ELM327::monitorDriveCycleStatus() { - if (queryPID(SERVICE_01, MONITOR_STATUS_THIS_DRIVE_CYCLE)) - return conditionResponse(findResponse(), 4); - - return ELM_GENERAL_ERROR; + return (uint32_t)processPID(SERVICE_01, MONITOR_STATUS_THIS_DRIVE_CYCLE, 1, 4); } - - - /* float ELM327::ctrlModVoltage() @@ -1579,15 +1774,9 @@ uint32_t ELM327::monitorDriveCycleStatus() */ float ELM327::ctrlModVoltage() { - if (queryPID(SERVICE_01, CONTROL_MODULE_VOLTAGE)) - return conditionResponse(findResponse(), 2, 1.0 / 1000.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, CONTROL_MODULE_VOLTAGE, 1, 2, 1.0 / 1000.0); } - - - /* float ELM327::absLoad() @@ -1605,15 +1794,9 @@ float ELM327::ctrlModVoltage() */ float ELM327::absLoad() { - if (queryPID(SERVICE_01, ABS_LOAD_VALUE)) - return conditionResponse(findResponse(), 2, 100.0 / 255.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, ABS_LOAD_VALUE, 1, 2, 100.0 / 255.0); } - - - /* float ELM327::commandedAirFuelRatio() @@ -1631,15 +1814,9 @@ float ELM327::absLoad() */ float ELM327::commandedAirFuelRatio() { - if (queryPID(SERVICE_01, FUEL_AIR_COMMANDED_EQUIV_RATIO)) - return conditionResponse(findResponse(), 2, 2.0 / 65536.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, FUEL_AIR_COMMANDED_EQUIV_RATIO, 1, 2, 2.0 / 65536.0); } - - - /* float ELM327::relativeThrottle() @@ -1657,15 +1834,9 @@ float ELM327::commandedAirFuelRatio() */ float ELM327::relativeThrottle() { - if (queryPID(SERVICE_01, RELATIVE_THROTTLE_POSITION)) - return conditionResponse(findResponse(), 1, 100.0 / 255.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, RELATIVE_THROTTLE_POSITION, 1, 1, 100.0 / 255.0); } - - - /* float ELM327::ambientAirTemp() @@ -1683,15 +1854,9 @@ float ELM327::relativeThrottle() */ float ELM327::ambientAirTemp() { - if (queryPID(SERVICE_01, AMBIENT_AIR_TEMP)) - return conditionResponse(findResponse(), 1, 1, -40); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, AMBIENT_AIR_TEMP, 1, 1, 1, -40); } - - - /* float ELM327::absThrottlePosB() @@ -1709,15 +1874,9 @@ float ELM327::ambientAirTemp() */ float ELM327::absThrottlePosB() { - if (queryPID(SERVICE_01, ABS_THROTTLE_POSITION_B)) - return conditionResponse(findResponse(), 1, 100.0 / 255.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, ABS_THROTTLE_POSITION_B, 1, 1, 100.0 / 255.0); } - - - /* float ELM327::absThrottlePosC() @@ -1735,15 +1894,9 @@ float ELM327::absThrottlePosB() */ float ELM327::absThrottlePosC() { - if (queryPID(SERVICE_01, ABS_THROTTLE_POSITION_C)) - return conditionResponse(findResponse(), 1, 100.0 / 255.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, ABS_THROTTLE_POSITION_C, 1, 1, 100.0 / 255.0); } - - - /* float ELM327::absThrottlePosD() @@ -1761,15 +1914,9 @@ float ELM327::absThrottlePosC() */ float ELM327::absThrottlePosD() { - if (queryPID(SERVICE_01, ABS_THROTTLE_POSITION_D)) - return conditionResponse(findResponse(), 1, 100.0 / 255.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, ABS_THROTTLE_POSITION_D, 1, 1, 100.0 / 255.0); } - - - /* float ELM327::absThrottlePosE() @@ -1787,15 +1934,9 @@ float ELM327::absThrottlePosD() */ float ELM327::absThrottlePosE() { - if (queryPID(SERVICE_01, ABS_THROTTLE_POSITION_E)) - return conditionResponse(findResponse(), 1, 100.0 / 255.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, ABS_THROTTLE_POSITION_E, 1, 1, 100.0 / 255.0); } - - - /* float ELM327::absThrottlePosF() @@ -1813,15 +1954,9 @@ float ELM327::absThrottlePosE() */ float ELM327::absThrottlePosF() { - if (queryPID(SERVICE_01, ABS_THROTTLE_POSITION_F)) - return conditionResponse(findResponse(), 1, 100.0 / 255.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, ABS_THROTTLE_POSITION_F, 1, 1, 100.0 / 255.0); } - - - /* float ELM327::commandedThrottleActuator() @@ -1839,15 +1974,9 @@ float ELM327::absThrottlePosF() */ float ELM327::commandedThrottleActuator() { - if (queryPID(SERVICE_01, COMMANDED_THROTTLE_ACTUATOR)) - return conditionResponse(findResponse(), 1, 100.0 / 255.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, COMMANDED_THROTTLE_ACTUATOR, 1, 1, 100.0 / 255.0); } - - - /* uint16_t ELM327::timeRunWithMIL() @@ -1865,15 +1994,9 @@ float ELM327::commandedThrottleActuator() */ uint16_t ELM327::timeRunWithMIL() { - if (queryPID(SERVICE_01, TIME_RUN_WITH_MIL_ON)) - return conditionResponse(findResponse(), 2); - - return ELM_GENERAL_ERROR; + return (uint16_t)processPID(SERVICE_01, TIME_RUN_WITH_MIL_ON, 1, 2); } - - - /* uint16_t ELM327::timeSinceCodesCleared() @@ -1891,15 +2014,9 @@ uint16_t ELM327::timeRunWithMIL() */ uint16_t ELM327::timeSinceCodesCleared() { - if (queryPID(SERVICE_01, TIME_SINCE_CODES_CLEARED)) - return conditionResponse(findResponse(), 2); - - return ELM_GENERAL_ERROR; + return (uint16_t)processPID(SERVICE_01, TIME_SINCE_CODES_CLEARED, 1, 2); } - - - /* float ELM327::maxMafRate() @@ -1917,15 +2034,9 @@ uint16_t ELM327::timeSinceCodesCleared() */ float ELM327::maxMafRate() { - if (queryPID(SERVICE_01, MAX_MAF_RATE)) - return conditionResponse(findResponse(), 1, 10.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, MAX_MAF_RATE, 1, 1, 10.0); } - - - /* uint8_t ELM327::fuelType() @@ -1943,17 +2054,11 @@ float ELM327::maxMafRate() */ uint8_t ELM327::fuelType() { - if (queryPID(SERVICE_01, FUEL_TYPE)) - return conditionResponse(findResponse(), 1); - - return ELM_GENERAL_ERROR; + return (uint8_t)processPID(SERVICE_01, FUEL_TYPE, 1, 1); } - - - /* - float ELM327::ethonolPercent() + float ELM327::ethanolPercent() Description: ------------ @@ -1967,17 +2072,11 @@ uint8_t ELM327::fuelType() ------- * float - Ethanol fuel in % */ -float ELM327::ethonolPercent() +float ELM327::ethanolPercent() { - if (queryPID(SERVICE_01, ETHONOL_FUEL_PERCENT)) - return conditionResponse(findResponse(), 1, 100.0 / 255.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, ETHANOL_FUEL_PERCENT, 1, 1, 100.0 / 255.0); } - - - /* float ELM327::absEvapSysVapPressure() @@ -1995,15 +2094,9 @@ float ELM327::ethonolPercent() */ float ELM327::absEvapSysVapPressure() { - if (queryPID(SERVICE_01, ABS_EVAP_SYS_VAPOR_PRESSURE)) - return conditionResponse(findResponse(), 2, 1.0 / 200.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, ABS_EVAP_SYS_VAPOR_PRESSURE, 1, 2, 1.0 / 200.0); } - - - /* float ELM327::evapSysVapPressure2() @@ -2021,15 +2114,9 @@ float ELM327::absEvapSysVapPressure() */ float ELM327::evapSysVapPressure2() { - if (queryPID(SERVICE_01, EVAP_SYS_VAPOR_PRESSURE)) - return conditionResponse(findResponse(), 2, 1, -32767); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, EVAP_SYS_VAPOR_PRESSURE, 1, 2, 1, -32767); } - - - /* float ELM327::absFuelRailPressure() @@ -2047,15 +2134,9 @@ float ELM327::evapSysVapPressure2() */ float ELM327::absFuelRailPressure() { - if (queryPID(SERVICE_01, FUEL_RAIL_ABS_PRESSURE)) - return conditionResponse(findResponse(), 2, 10.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, FUEL_RAIL_ABS_PRESSURE, 1, 2, 10.0); } - - - /* float ELM327::relativePedalPos() @@ -2073,15 +2154,9 @@ float ELM327::absFuelRailPressure() */ float ELM327::relativePedalPos() { - if (queryPID(SERVICE_01, RELATIVE_ACCELERATOR_PEDAL_POS)) - return conditionResponse(findResponse(), 1, 100.0 / 255.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, RELATIVE_ACCELERATOR_PEDAL_POS, 1, 1, 100.0 / 255.0); } - - - /* float ELM327::hybridBatLife() @@ -2099,15 +2174,9 @@ float ELM327::relativePedalPos() */ float ELM327::hybridBatLife() { - if (queryPID(SERVICE_01, HYBRID_BATTERY_REMAINING_LIFE)) - return conditionResponse(findResponse(), 1, 100.0 / 255.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, HYBRID_BATTERY_REMAINING_LIFE, 1, 1, 100.0 / 255.0); } - - - /* float ELM327::oilTemp() @@ -2125,15 +2194,9 @@ float ELM327::hybridBatLife() */ float ELM327::oilTemp() { - if (queryPID(SERVICE_01, ENGINE_OIL_TEMP)) - return conditionResponse(findResponse(), 1, 1, -40.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, ENGINE_OIL_TEMP, 1, 1, 1, -40.0); } - - - /* float ELM327::fuelInjectTiming() @@ -2151,15 +2214,9 @@ float ELM327::oilTemp() */ float ELM327::fuelInjectTiming() { - if (queryPID(SERVICE_01, FUEL_INJECTION_TIMING)) - return conditionResponse(findResponse(), 2, 1.0 / 128.0, -210.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, FUEL_INJECTION_TIMING, 1, 2, 1.0 / 128.0, -210.0); } - - - /* float ELM327::fuelRate() @@ -2177,15 +2234,9 @@ float ELM327::fuelInjectTiming() */ float ELM327::fuelRate() { - if (queryPID(SERVICE_01, ENGINE_FUEL_RATE)) - return conditionResponse(findResponse(), 2, 1.0 / 20.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, ENGINE_FUEL_RATE, 1, 2, 1.0 / 20.0); } - - - /* uint8_t ELM327::emissionRqmts() @@ -2203,15 +2254,9 @@ float ELM327::fuelRate() */ uint8_t ELM327::emissionRqmts() { - if (queryPID(SERVICE_01, EMISSION_REQUIREMENTS)) - return conditionResponse(findResponse(), 1); - - return ELM_GENERAL_ERROR; + return (uint8_t)processPID(SERVICE_01, EMISSION_REQUIREMENTS, 1, 1); } - - - /* uint32_t ELM327::supportedPIDs_61_80() @@ -2229,15 +2274,9 @@ uint8_t ELM327::emissionRqmts() */ uint32_t ELM327::supportedPIDs_61_80() { - if (queryPID(SERVICE_01, SUPPORTED_PIDS_61_80)) - return conditionResponse(findResponse(), 4); - - return ELM_GENERAL_ERROR; + return (uint32_t)processPID(SERVICE_01, SUPPORTED_PIDS_61_80, 1, 4); } - - - /* float ELM327::demandedTorque() @@ -2255,15 +2294,9 @@ uint32_t ELM327::supportedPIDs_61_80() */ float ELM327::demandedTorque() { - if (queryPID(SERVICE_01, DEMANDED_ENGINE_PERCENT_TORQUE)) - return conditionResponse(findResponse(), 1, 1, -125.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, DEMANDED_ENGINE_PERCENT_TORQUE, 1, 1, 1, -125.0); } - - - /* float ELM327::torque() @@ -2281,15 +2314,9 @@ float ELM327::demandedTorque() */ float ELM327::torque() { - if (queryPID(SERVICE_01, ACTUAL_ENGINE_TORQUE)) - return conditionResponse(findResponse(), 1, 1, -125.0); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, ACTUAL_ENGINE_TORQUE, 1, 1, 1, -125.0); } - - - /* uint16_t ELM327::referenceTorque() @@ -2307,15 +2334,9 @@ float ELM327::torque() */ uint16_t ELM327::referenceTorque() { - if (queryPID(SERVICE_01, ENGINE_REFERENCE_TORQUE)) - return conditionResponse(findResponse(), 2); - - return ELM_GENERAL_ERROR; + return processPID(SERVICE_01, ENGINE_REFERENCE_TORQUE, 1, 2); } - - - /* uint16_t ELM327::auxSupported() @@ -2333,21 +2354,60 @@ uint16_t ELM327::referenceTorque() */ uint16_t ELM327::auxSupported() { - if (queryPID(SERVICE_01, AUX_INPUT_OUTPUT_SUPPORTED)) - return conditionResponse(findResponse(), 2); - - return ELM_GENERAL_ERROR; + return (uint16_t)processPID(SERVICE_01, AUX_INPUT_OUTPUT_SUPPORTED, 1, 2); } +/* + void ELM327::sendCommand(const char *cmd) + Description: + ------------ + * Sends a command/query for Non-Blocking PID queries + + Inputs: + ------- + * const char *cmd - Command/query to send to ELM327 + Return: + ------- + * void +*/ +void ELM327::sendCommand(const char *cmd) +{ + // clear payload buffer + memset(payload, '\0', PAYLOAD_LEN + 1); + + // reset input serial buffer and number of received bytes + recBytes = 0; + flushInputBuff(); + connected = false; + + // Reset the receive state ready to start receiving a response message + nb_rx_state = ELM_GETTING_MSG; + + if (debugMode) + { + Serial.print(F("Sending the following command/query: ")); + Serial.println(cmd); + } + + elm_port->print(cmd); + elm_port->print('\r'); + + // prime the timeout timer + previousTime = millis(); + currentTime = previousTime; +} /* - int8_t ELM327::sendCommand(const char *cmd) + obd_rx_states ELM327::sendCommand_Blocking(const char* cmd) Description: ------------ - * Sends a command/query and reads/buffers the ELM327's response + * Sends a command/query and waits for a respoonse (blocking function) + Sometimes it's desirable to use a blocking command, e.g when sending an AT command. + This function removes the need for the caller to set up a loop waiting for the command to finish. + Caller is free to parse the payload string if they need to use the response. Inputs: ------- @@ -2355,147 +2415,287 @@ uint16_t ELM327::auxSupported() Return: ------- - * int8_t - Response status + * int8_t - the ELM_XXX status of getting the OBD response */ -int8_t ELM327::sendCommand(const char *cmd) +int8_t ELM327::sendCommand_Blocking(const char *cmd) { - uint8_t counter = 0; - connected = false; - - // clear payload buffer - memset(payload, '\0', PAYLOAD_LEN + 1); - - // reset input serial buffer and number of received bytes - recBytes = 0; - flushInputBuff(); + sendCommand(cmd); + uint32_t startTime = millis(); + while (get_response() == ELM_GETTING_MSG) { + if (millis() - startTime > timeout_ms) break; + } + return nb_rx_state; +} - if (debugMode) - { - Serial.print(F("Sending the following command/query: ")); - Serial.println(cmd); - } +/* + obd_rx_states ELM327::get_response(void) - elm_port->print(cmd); - elm_port->print('\r'); + Description: + ------------ + * Non Blocking (NB) receive OBD scanner response. Must be called repeatedly until + the status progresses past ELM_GETTING_MSG. - // prime the timeout timer - previousTime = millis(); - currentTime = previousTime; + Inputs: + ------- + * void - // buffer the response of the ELM327 until either the - // end marker is read or a timeout has occurred - // last valid idx is PAYLOAD_LEN but want to keep on free for terminating '\0' + Return: + ------- + * int8_t - the ELM_XXX status of getting the OBD response +*/ +int8_t ELM327::get_response(void) +{ + // buffer the response of the ELM327 until either the + // end marker is read or a timeout has occurred + // last valid idx is PAYLOAD_LEN but want to keep one free for terminating '\0' // so limit counter to < PAYLOAD_LEN - while ((counter < PAYLOAD_LEN) && !timeout()) - { - if (elm_port->available()) - { - char recChar = elm_port->read(); - - if (debugMode) - { - Serial.print(F("\tReceived char: ")); - - if (recChar == '\f') - Serial.println(F("\\f")); - else if (recChar == '\n') - Serial.println(F("\\n")); - else if (recChar == '\r') - Serial.println(F("\\r")); - else if (recChar == '\t') - Serial.println(F("\\t")); - else if (recChar == '\v') - Serial.println(F("\\v")); - else - Serial.println(recChar); - } - - if (recChar == '>') - { - if (debugMode) - Serial.println(F("Delimiter found")); - - break; - } - else if (!isalnum(recChar)) - continue; - - payload[counter] = recChar; - counter++; - } - } - - if (debugMode) - { - Serial.print(F("All chars received: ")); - Serial.println(payload); - } - - if (timeout()) - { - if (debugMode) - { - Serial.print(F("Timeout detected with overflow of ")); - Serial.print((currentTime - previousTime) - timeout_ms); - Serial.println(F("ms")); - } - - status = ELM_TIMEOUT; - return status; - } - - if (nextIndex(payload, "UNABLETOCONNECT") >= 0) - { - if (debugMode) - Serial.println(F("ELM responded with errror \"UNABLE TO CONNECT\"")); - - status = ELM_UNABLE_TO_CONNECT; - return status; - } - - connected = true; - - if (nextIndex(payload, "NODATA") >= 0) - { - if (debugMode) - Serial.println(F("ELM responded with errror \"NO DATA\"")); - - status = ELM_NO_DATA; - return status; - } - - if (nextIndex(payload, "STOPPED") >= 0) - { - if (debugMode) - Serial.println(F("ELM responded with errror \"STOPPED\"")); - - status = ELM_STOPPED; - return status; - } - - if (nextIndex(payload, "ERROR") >= 0) - { - if (debugMode) - Serial.println(F("ELM responded with \"ERROR\"")); - - status = ELM_GENERAL_ERROR; - return status; - } - - // keep track of how many bytes were received in - // the ELM327's response (not counting the - // end-marker '>') if a valid response is found - recBytes = counter; - - status = ELM_SUCCESS; - return status; -} - - - - -/* - uint64_t ELM327::findResponse() + if (!elm_port->available()) + { + nb_rx_state = ELM_GETTING_MSG; + if (timeout()) + nb_rx_state = ELM_TIMEOUT; + } + else + { + char recChar = elm_port->read(); + + if (debugMode) + { + Serial.print(F("\tReceived char: ")); + // display each received character, make non-printables printable + if (recChar == '\f') + Serial.println(F("\\f")); + else if (recChar == '\n') + Serial.println(F("\\n")); + else if (recChar == '\r') + Serial.println(F("\\r")); + else if (recChar == '\t') + Serial.println(F("\\t")); + else if (recChar == '\v') + Serial.println(F("\\v")); + // convert spaces to underscore, easier to see in debug output + else if (recChar == ' ') + Serial.println(F("_")); + // display regular printable + else + Serial.println(recChar); + } + + // this is the end of the OBD response + if (recChar == '>') + { + if (debugMode) + Serial.println(F("Delimiter found.")); + + nb_rx_state = ELM_MSG_RXD; + } + else if (!isalnum(recChar) && (recChar != ':') && (recChar != '.') && (recChar != '\r')) + // Keep only alphanumeric, decimal, colon, CR. These are needed for response parsing + // decimal places needed to extract floating point numbers, e.g. battery voltage + nb_rx_state = ELM_GETTING_MSG; // Discard this character + else + { + if (recBytes < PAYLOAD_LEN) + { + payload[recBytes] = recChar; + recBytes++; + nb_rx_state = ELM_GETTING_MSG; + } + else + nb_rx_state = ELM_BUFFER_OVERFLOW; + } + } + + // Message is still being received (or is timing out), so exit early without doing all the other checks + if (nb_rx_state == ELM_GETTING_MSG) + return nb_rx_state; + + // End of response delimiter was found + if (debugMode && nb_rx_state == ELM_MSG_RXD) + { + Serial.print(F("All chars received: ")); + Serial.println(payload); + } + + if (nb_rx_state == ELM_TIMEOUT) + { + if (debugMode) + { + Serial.print(F("Timeout detected with overflow of ")); + Serial.print((currentTime - previousTime) - timeout_ms); + Serial.println(F("ms")); + } + return nb_rx_state; + } + + if (nb_rx_state == ELM_BUFFER_OVERFLOW) + { + if (debugMode) + { + Serial.print(F("OBD receive buffer overflow (> ")); + Serial.print(PAYLOAD_LEN); + Serial.println(F(" bytes)")); + } + return nb_rx_state; + } + + // Now we have successfully received OBD response, check if the payload indicates any OBD errors + if (nextIndex(payload, RESPONSE_UNABLE_TO_CONNECT) >= 0) + { + if (debugMode) + Serial.println(F("ELM responded with error \"UNABLE TO CONNECT\"")); + + nb_rx_state = ELM_UNABLE_TO_CONNECT; + return nb_rx_state; + } + + connected = true; + + if (nextIndex(payload, RESPONSE_NO_DATA) >= 0) + { + if (debugMode) + Serial.println(F("ELM responded with error \"NO DATA\"")); + + nb_rx_state = ELM_NO_DATA; + return nb_rx_state; + } + + if (nextIndex(payload, RESPONSE_STOPPED) >= 0) + { + if (debugMode) + Serial.println(F("ELM responded with error \"STOPPED\"")); + + nb_rx_state = ELM_STOPPED; + return nb_rx_state; + } + + if (nextIndex(payload, RESPONSE_ERROR) >= 0) + { + if (debugMode) + Serial.println(F("ELM responded with \"ERROR\"")); + + nb_rx_state = ELM_GENERAL_ERROR; + return nb_rx_state; + } + + nb_rx_state = ELM_SUCCESS; + // Need to process multiline repsonses, remove '\r' from non multiline resp + if (NULL != strchr(payload, ':')) { + parseMultiLineResponse(); + } + else { + removeChar(payload, " \r"); + } + recBytes = strlen(payload); + return nb_rx_state; +} + +/* + void ELM327::parseMultilineResponse() + + Description: + ------------ + * Parses a buffered multiline response into a single line with the specified data + * Modifies the value of payload for further processing and removes the '\r' chars + + Inputs: + ------- + * void + + Return: + ------- + * void +*/ +void ELM327::parseMultiLineResponse() { + uint8_t totalBytes = 0; + uint8_t bytesReceived = 0; + char newResponse[PAYLOAD_LEN]; + memset(newResponse, 0, PAYLOAD_LEN * sizeof(char)); // Initialize newResponse to empty string + char line[256] = ""; + char* start = payload; + char* end = strchr(start, '\r'); + + do + { //Step 1: Get a line from the response + memset(line, '\0', 256); + if (end != NULL) { + strncpy(line, start, end - start); + line[end - start] = '\0'; + } else { + strncpy(line, start, strlen(start)); + line[strlen(start)] = '\0'; + + // Exit when there's no more data + if (strlen(line) == 0) break; + } + + if (debugMode) { + Serial.print(F("Found line in response: ")); + Serial.println(line); + } + // Step 2: Check if this is the first line of the response + if (0 == totalBytes) + // Some devices return the response header in the first line instead of the data length, ignore this line + // Line containing totalBytes indicator is 3 hex chars only, longer first line will be a header. + { + if (strlen(line) > 3) { + if (debugMode) + { + Serial.print(F("Found header in response line: ")); + Serial.println(line); + } + } + else { + if (strlen(line) > 0) { + totalBytes = strtol(line, NULL, 16) * 2; + if (debugMode) { + Serial.print(F("totalBytes = ")); + Serial.println(totalBytes); + } + } + } + } + // Step 3: Process data response lines + else { + if (strchr(line, ':')) { + char* dataStart = strchr(line, ':') + 1; + uint8_t dataLength = strlen(dataStart); + uint8_t bytesToCopy = (bytesReceived + dataLength > totalBytes) ? (totalBytes - bytesReceived) : dataLength; + if (bytesReceived + bytesToCopy > PAYLOAD_LEN - 1) { + bytesToCopy = (PAYLOAD_LEN - 1) - bytesReceived; + } + strncat(newResponse, dataStart, bytesToCopy); + bytesReceived += bytesToCopy; + + if (debugMode) { + Serial.print(F("Response data: ")); + Serial.println(dataStart); + } + } + } + if (*(end + 1) == '\0') { + start = NULL; + } else { + start = end + 1; + } + end = (start != NULL) ? strchr(start, '\r') : NULL; + + } while ((bytesReceived < totalBytes || 0 == totalBytes) && start != NULL); + + // Replace payload with parsed response, null-terminate after totalBytes + int nullTermPos = (totalBytes < PAYLOAD_LEN - 1) ? totalBytes : PAYLOAD_LEN - 1; + strncpy(payload, newResponse, nullTermPos); + payload[nullTermPos] = '\0'; // Ensure null termination + if (debugMode) + { + Serial.print(F("Parsed multiline response: ")); + Serial.println(payload); + } +} + + +/* + uint64_t ELM327::findResponse(const uint8_t& service, const uint8_t& pid) Description: ------------ @@ -2503,123 +2703,139 @@ int8_t ELM327::sendCommand(const char *cmd) Inputs: ------- - * void + * const uint8_t& service - The diagnostic service ID. 01 is "Show current data" + * const uint8_t& pid - The Parameter ID (PID) from the service Return: ------- - * uint64_t - Query response value + * void */ uint64_t ELM327::findResponse() { - uint8_t firstDatum = 0; - char header[7] = { '\0' }; - - if (longQuery) - { - header[0] = query[0] + 4; - header[1] = query[1]; - header[2] = query[2]; - header[3] = query[3]; - header[4] = query[4]; - header[5] = query[5]; - } - else - { - header[0] = query[0] + 4; - header[1] = query[1]; - header[2] = query[2]; - header[3] = query[3]; - } - - if (debugMode) - { - Serial.print(F("Expected response header: ")); - Serial.println(header); - } - - int8_t firstHeadIndex = nextIndex(payload, header); - int8_t secondHeadIndex = nextIndex(payload, header, 2); - - if (firstHeadIndex >= 0) - { - if (longQuery) - firstDatum = firstHeadIndex + 6; - else - firstDatum = firstHeadIndex + 4; - - // Some ELM327s (such as my own) respond with two - // "responses" per query. "numPayChars" represents the - // correct number of bytes returned by the ELM327 - // regardless of how many "responses" were returned - if (secondHeadIndex >= 0) - { - if (debugMode) - Serial.println(F("Double response detected")); - - numPayChars = secondHeadIndex - firstDatum; - } - else - { - if (debugMode) - Serial.println(F("Single response detected")); - - numPayChars = recBytes - firstDatum; - } - - response = 0; - for(uint8_t i = 0; i < numPayChars; i++) - { - uint8_t payloadIndex = firstDatum + i; - uint8_t bitsOffset = 4 * (numPayChars - i - 1); - response = response | (ctoi(payload[payloadIndex]) << bitsOffset); - } - - // It is usefull to have the response bytes - // broken-out because some PID algorithms (standard - // and custom) require special operations for each - // byte returned - responseByte_0 = response & 0xFF; - responseByte_1 = (response >> 8) & 0xFF; - responseByte_2 = (response >> 16) & 0xFF; - responseByte_3 = (response >> 24) & 0xFF; - responseByte_4 = (response >> 32) & 0xFF; - responseByte_5 = (response >> 40) & 0xFF; - responseByte_6 = (response >> 48) & 0xFF; - responseByte_7 = (response >> 56) & 0xFF; - - if (debugMode) - { - Serial.println(F("64-bit response: ")); - Serial.print(F("\tresponseByte_0: ")); - Serial.println(responseByte_0); - Serial.print(F("\tresponseByte_1: ")); - Serial.println(responseByte_1); - Serial.print(F("\tresponseByte_2: ")); - Serial.println(responseByte_2); - Serial.print(F("\tresponseByte_3: ")); - Serial.println(responseByte_3); - Serial.print(F("\tresponseByte_4: ")); - Serial.println(responseByte_4); - Serial.print(F("\tresponseByte_5: ")); - Serial.println(responseByte_5); - Serial.print(F("\tresponseByte_6: ")); - Serial.println(responseByte_6); - Serial.print(F("\tresponseByte_7: ")); - Serial.println(responseByte_7); - } - - return response; - } - - if (debugMode) - Serial.println(F("Response not detected")); - - return 0; + uint8_t firstDatum = 0; + char header[7] = {'\0'}; + + if (longQuery) + { + header[0] = query[0] + 4; + header[1] = query[1]; + header[2] = query[2]; + header[3] = query[3]; + header[4] = query[4]; + header[5] = query[5]; + } + else + { + header[0] = query[0] + 4; + header[1] = query[1]; + + if (isMode0x22Query) // mode 0x22 responses always zero-pad the pid to 4 chars, even for a 2-char pid + { + header[2] = '0'; + header[3] = '0'; + header[4] = query[2]; + header[5] = query[3]; + } + else + { + header[2] = query[2]; + header[3] = query[3]; + } + } + + if (debugMode) + { + Serial.print(F("Expected response header: ")); + Serial.println(header); + } + + int8_t firstHeadIndex = nextIndex(payload, header, 1); + int8_t secondHeadIndex = nextIndex(payload, header, 2); + + if (firstHeadIndex >= 0) + { + if (longQuery | isMode0x22Query) + firstDatum = firstHeadIndex + 6; + else + firstDatum = firstHeadIndex + 4; + + // Some ELM327s (such as my own) respond with two + // "responses" per query. "numPayChars" represents the + // correct number of bytes returned by the ELM327 + // regardless of how many "responses" were returned + if (secondHeadIndex >= 0) + { + if (debugMode) + Serial.println(F("Double response detected")); + + numPayChars = secondHeadIndex - firstDatum; + } + else + { + if (debugMode) + Serial.println(F("Single response detected")); + + numPayChars = strlen(payload) - firstDatum; + } + + response = 0; + for (uint8_t i = 0; i < numPayChars; i++) + { + uint8_t payloadIndex = firstDatum + i; + uint8_t bitsOffset = 4 * (numPayChars - i - 1); + + if (debugMode) + { + Serial.print(F("\tProcessing hex nibble: ")); + Serial.println(payload[payloadIndex]); + } + response = response | ((uint64_t)ctoi(payload[payloadIndex]) << bitsOffset); + } + + // It is useful to have the response bytes + // broken-out because some PID algorithms (standard + // and custom) require special operations for each + // byte returned + + responseByte_0 = response & 0xFF; + responseByte_1 = (response >> 8) & 0xFF; + responseByte_2 = (response >> 16) & 0xFF; + responseByte_3 = (response >> 24) & 0xFF; + responseByte_4 = (response >> 32) & 0xFF; + responseByte_5 = (response >> 40) & 0xFF; + responseByte_6 = (response >> 48) & 0xFF; + responseByte_7 = (response >> 56) & 0xFF; + + if (debugMode) + { + Serial.println(F("64-bit response: ")); + Serial.print(F("\tresponseByte_0: ")); + Serial.println(responseByte_0); + Serial.print(F("\tresponseByte_1: ")); + Serial.println(responseByte_1); + Serial.print(F("\tresponseByte_2: ")); + Serial.println(responseByte_2); + Serial.print(F("\tresponseByte_3: ")); + Serial.println(responseByte_3); + Serial.print(F("\tresponseByte_4: ")); + Serial.println(responseByte_4); + Serial.print(F("\tresponseByte_5: ")); + Serial.println(responseByte_5); + Serial.print(F("\tresponseByte_6: ")); + Serial.println(responseByte_6); + Serial.print(F("\tresponseByte_7: ")); + Serial.println(responseByte_7); + } + + return response; + } + + if (debugMode) + Serial.println(F("Response not detected")); + + return 0; } - - - /* void ELM327::printError() @@ -2637,27 +2853,506 @@ uint64_t ELM327::findResponse() */ void ELM327::printError() { - Serial.print(F("Received: ")); - Serial.println(payload); - - if (status == ELM_SUCCESS) - Serial.println(F("ELM_SUCCESS")); - else if (status == ELM_NO_RESPONSE) - Serial.println(F("ERROR: ELM_NO_RESPONSE")); - else if (status == ELM_BUFFER_OVERFLOW) - Serial.println(F("ERROR: ELM_BUFFER_OVERFLOW")); - else if (status == ELM_UNABLE_TO_CONNECT) - Serial.println(F("ERROR: ELM_UNABLE_TO_CONNECT")); - else if (status == ELM_NO_DATA) - Serial.println(F("ERROR: ELM_NO_DATA")); - else if (status == ELM_STOPPED) - Serial.println(F("ERROR: ELM_STOPPED")); - else if (status == ELM_TIMEOUT) - Serial.println(F("ERROR: ELM_TIMEOUT")); - else if (status == ELM_TIMEOUT) - Serial.println(F("ERROR: ELM_GENERAL_ERROR")); - else - Serial.println(F("No error detected")); - - delay(100); + Serial.print(F("Received: ")); + Serial.println(payload); + + if (nb_rx_state == ELM_SUCCESS) + Serial.println(F("ELM_SUCCESS")); + else if (nb_rx_state == ELM_NO_RESPONSE) + Serial.println(F("ERROR: ELM_NO_RESPONSE")); + else if (nb_rx_state == ELM_BUFFER_OVERFLOW) + Serial.println(F("ERROR: ELM_BUFFER_OVERFLOW")); + else if (nb_rx_state == ELM_UNABLE_TO_CONNECT) + Serial.println(F("ERROR: ELM_UNABLE_TO_CONNECT")); + else if (nb_rx_state == ELM_NO_DATA) + Serial.println(F("ERROR: ELM_NO_DATA")); + else if (nb_rx_state == ELM_STOPPED) + Serial.println(F("ERROR: ELM_STOPPED")); + else if (nb_rx_state == ELM_TIMEOUT) + Serial.println(F("ERROR: ELM_TIMEOUT")); + else if (nb_rx_state == ELM_BUFFER_OVERFLOW) + Serial.println(F("ERROR: BUFFER OVERFLOW")); + else if (nb_rx_state == ELM_GENERAL_ERROR) + Serial.println(F("ERROR: ELM_GENERAL_ERROR")); + else + Serial.println(F("No error detected")); + + delay(100); +} + +/* + float ELM327::batteryVoltage() + + Description: + ------------ + * Get the current vehicle battery voltage in Volts DC + + Inputs: + ------- + * void + + Return: + ------- + * float - vehicle battery voltage in VDC +*/ +float ELM327::batteryVoltage() +{ + if (nb_query_state == SEND_COMMAND) + { + sendCommand(READ_VOLTAGE); + nb_query_state = WAITING_RESP; + } + else if (nb_query_state == WAITING_RESP) + { + get_response(); + if (nb_rx_state == ELM_SUCCESS) + { + nb_query_state = SEND_COMMAND; // Reset the query state machine for next command + payload[strlen(payload) - 1] = '\0'; // Remove the last char ("V") from the payload value + + if (strncmp(payload, "ATRV", 4) == 0) + return (float)strtod(payload + 4, NULL); + else + return (float)strtod(payload, NULL); + } + else if (nb_rx_state != ELM_GETTING_MSG) + nb_query_state = SEND_COMMAND; // Error or timeout, so reset the query state machine for next command + } + return 0.0; +} + +/* + int8_t ELM327::get_vin_blocking(char *vin) + + Description: + ------------ + * Read Vehicle Identification Number (VIN). This is a blocking function. + + Inputs: + ------- + * char vin[] - pointer to c-string in which to store VIN + Note: (allocate memory for 18 character c-string in calling function) + + Return: + ------- + * int8_t - the ELM_XXX status of getting the VIN +*/ +int8_t ELM327::get_vin_blocking(char vin[]) +{ + char temp[3] = {0}; + char *idx; + uint8_t vin_counter = 0; + uint8_t ascii_val; + + if (debugMode) + Serial.println(F("Getting VIN...")); + + sendCommand("0902"); // VIN is command 0902 + while (get_response() == ELM_GETTING_MSG) + ; + + // strcpy(payload, "0140:4902013144341:475030305235352:42313233343536"); + if (nb_rx_state == ELM_SUCCESS) + { + memset(vin, 0, 18); + // **** Decoding **** + if (strstr(payload, "490201")) + { + // OBD scanner provides this multiline response: + // 014 ==> 0x14 = 20 bytes following + // 0: 49 02 01 31 44 34 ==> 49 02 = Header. 01 = 1 VIN number in message. 31, 44, 34 = First 3 VIN digits + // 1: 47 50 30 30 52 35 35 ==> 47->35 next 7 VIN digits + // 2: 42 31 32 33 34 35 36 ==> 42->36 next 7 VIN digits + // + // The resulitng payload buffer is: + // "0140:4902013144341:475030305235352:42313233343536" ==> VIN="1D4GP00R55B123456" (17-digits) + idx = strstr(payload, "490201") + 6; // Pointer to first ASCII code digit of first VIN digit + // Loop over each pair of ASCII code digits. 17 VIN digits + 2 skipped line numbers = 19 loops + for (int i = 0; i < (19 * 2); i += 2) + { + temp[0] = *(idx + i); // Get first digit of ASCII code + temp[1] = *(idx + i + 1); // Get second digit of ASCII code + // No need to add string termination, temp[3] always == 0 + + if (strstr(temp, ":")) + continue; // Skip the second "1:" and third "2:" line numbers + + ascii_val = strtol(temp, 0, 16); // Convert ASCII code to integer + snprintf(vin + vin_counter++, sizeof(uint8_t), "%c", ascii_val); // Convert ASCII code integer back to character + // Serial.printf("Chars %s, ascii_val=%d[dec] 0x%02hhx[hex] ==> VIN=%s\n", temp, ascii_val, ascii_val, vin); + } + } + if (debugMode) + { + Serial.print(F("VIN: ")); + Serial.println(vin); + } + } + else + { + if (debugMode) + { + Serial.println(F("No VIN response")); + printError(); + } + } + return nb_rx_state; +} + +/* + bool ELM327::resetDTC() + + Description: + ------------ + * Resets the stored DTCs in the ECU. This is a blocking function. + Note: The SAE spec requires that scan tools verify that a reset + is intended ("Are you sure?") before sending the mode 04 + reset command to the vehicle. See p.32 of ELM327 datasheet. + + Inputs: + ------- + * void + + Return: + ------- + * bool - Indicates the success (or not) of the reset command. +*/ +bool ELM327::resetDTC() +{ + if (sendCommand_Blocking("04") == ELM_SUCCESS) + { + if (strstr(payload, "44") != NULL) + { + if (debugMode) + Serial.println(F("ELMduino: DTC successfully reset.")); + + return true; + } + } + else + { + if (debugMode) + Serial.println(F("ELMduino: Resetting DTC codes failed.")); + } + + return false; +} + +/* + void ELM327::currentDTCCodes(const bool& isBlocking) + + Description: + ------------ + * Get the list of current DTC codes. This method is blocking by default, but can be run + in non-blocking mode if desired with optional boolean argument. Typical use involves + calling the monitorStatus() function first to get the number of DTC current codes stored, + then calling this function to retrieve those codes. This would not typically + be done in NB mode in a loop, but optional NB mode is supported. + + * To check the results of this query, inspect the DTC_Response struct: DTC_Response.codesFound + will contain the number of codes present and DTC_Response.codes is an array + of 5 char codes that were retrieved. + + Inputs: + ------- + * bool isBlocking - optional arg to set (non)blocking mode - defaults to true / blocking mode + + Return: + ------- + * void +*/ +void ELM327::currentDTCCodes(const bool& isBlocking) +{ + char *idx; + char codeType = '\0'; + char codeNumber[5] = {0}; + char temp[6] = {0}; + + if (isBlocking) // In blocking mode, we loop here until get_response() is past ELM_GETTING_MSG state + { + sendCommand("03"); // Check DTC is always Service 03 with no PID + while (get_response() == ELM_GETTING_MSG) + ; + } + else + { + if (nb_query_state == SEND_COMMAND) + { + sendCommand("03"); + nb_query_state = WAITING_RESP; + } + + else if (nb_query_state == WAITING_RESP) + get_response(); + } + + if (nb_rx_state == ELM_SUCCESS) + { + nb_query_state = SEND_COMMAND; // Reset the query state machine for next command + memset(DTC_Response.codes, 0, DTC_CODE_LEN * DTC_MAX_CODES); + + if (strstr(payload, "43") != NULL) // Successful response to Mode 03 request + { + // OBD scanner will provide a response that contains one or more lines indicating the codes present. + // Each response line will start with "43" indicating it is a response to a Mode 03 request. + // See p. 31 of ELM327 datasheet for details and lookup table of code types. + + uint8_t codesFound = strlen(payload) / 8; // Each code found returns 8 chars starting with "43" + idx = strstr(payload, "43") + 4; // Pointer to first DTC code digit (third char in the response) + + if (codesFound > DTC_MAX_CODES) // I don't think the ELM is capable of returning + { // more than 0xF (16) codes, but just in case... + codesFound = DTC_MAX_CODES; + Serial.print(F("DTC response truncated at ")); + Serial.print(DTC_MAX_CODES); + Serial.println(F(" codes.")); + } + + DTC_Response.codesFound = codesFound; + + for (int i = 0; i < codesFound; i++) + { + memset(temp, 0, sizeof(temp)); + memset(codeNumber, 0, sizeof(codeNumber)); + + codeType = *idx; // Get first digit of second byte + codeNumber[0] = *(idx + 1); // Get second digit of second byte + codeNumber[1] = *(idx + 2); // Get first digit of third byte + codeNumber[2] = *(idx + 3); // Get second digit of third byte + + switch (codeType) // Set the correct type prefix for the code + { + case '0': + strcat(temp, "P0"); + break; + + case '1': + strcat(temp, "P1"); + break; + + case '2': + strcat(temp, "P2"); + break; + case '3': + strcat(temp, "P3"); + break; + + case '4': + strcat(temp, "C0"); + break; + + case '5': + strcat(temp, "C1"); + break; + + case '6': + strcat(temp, "C2"); + break; + + case '7': + strcat(temp, "C3"); + break; + + case '8': + strcat(temp, "B0"); + break; + + case '9': + strcat(temp, "B1"); + break; + + case 'A': + strcat(temp, "B2"); + break; + + case 'B': + strcat(temp, "B3"); + break; + + case 'C': + strcat(temp, "U0"); + break; + + case 'D': + strcat(temp, "U1"); + break; + + case 'E': + strcat(temp, "U2"); + break; + + case 'F': + strcat(temp, "U3"); + break; + + default: + break; + } + + strcat(temp, codeNumber); // Append the code number to the prefix + strcpy(DTC_Response.codes[i], temp); // Add the fully parsed code to the list (array) + idx = idx + 8; // reset idx to start of next code + + if (debugMode) + { + Serial.print(F("ELMduino: Found code: ")); + Serial.println(temp); + } + } + } + else + { + if (debugMode) + { + Serial.println(F("ELMduino: DTC response received with no valid data.")); + } + } + return; + } + else if (nb_rx_state != ELM_GETTING_MSG) + { + nb_query_state = SEND_COMMAND; // Error or timeout, so reset the query state machine for next command + + if (debugMode) + { + Serial.println(F("ELMduino: Getting current DTC codes failed.")); + printError(); + } + } +} + +/* + bool ELM327::isPidSupported(uint8_t pid) + + Description: + ------------ + * Checks if a particular PID is supported by the connected ECU. + + * This is a convenience method that selects the correct supportedPIDS_xx_xx() query and parses + the bit-encoded result, returning a simple Boolean value indicating PID support from the ECU. + + Inputs: + ------- + * uint8_t pid - the PID to check for support. + + Return: + ------- + * bool - Whether or not the queried PID is supported by the ECU. +*/ +bool ELM327::isPidSupported(uint8_t pid) +{ + uint8_t pidInterval = (pid / PID_INTERVAL_OFFSET) * PID_INTERVAL_OFFSET; + + switch (pidInterval) + { + case SUPPORTED_PIDS_1_20: + supportedPIDs_1_20(); + break; + + case SUPPORTED_PIDS_21_40: + supportedPIDs_21_40(); + pid = (pid - SUPPORTED_PIDS_21_40); + break; + + case SUPPORTED_PIDS_41_60: + supportedPIDs_41_60(); + pid = (pid - SUPPORTED_PIDS_41_60); + break; + + case SUPPORTED_PIDS_61_80: + supportedPIDs_61_80(); + pid = (pid - SUPPORTED_PIDS_61_80); + break; + + default: + break; + } + + if (nb_rx_state == ELM_SUCCESS) + { + return ((response >> (32 - pid)) & 0x1); + } + return false; +} + +double ELM327::calculator_0C() { + return (double)((response_A << 8) | response_B)/4; +} + +double ELM327::calculator_10() { + return (double)((response_A << 8) | response_B)/100; +} + +double ELM327::calculator_14(){ + return (double)(response_A/200) ; +} + +double ELM327::calculator_1F() { + return (double)((response_A << 8) | response_B); +} + +double ELM327::calculator_22() { + return (double) ((response_A << 8) | response_B) * 0.079; +} + +double ELM327::calculator_23() { + return (double) ((response_A << 8) | response_B) * 10; +} + +double ELM327::calculator_32() +{ + return (double) ((int16_t)((response_A << 8) | response_B)) / 4.0; +} + +double ELM327::calculator_3C() { + return (double) (((response_A << 8) | response_B) / 10) - 40; +} + +double ELM327::calculator_42() { + return (double) ((response_A << 8) | response_B) / 1000; +} + +double ELM327::calculator_43() { + return (double) ((response_A << 8) | response_B) * (100.0 / 255.0); +} + +double ELM327::calculator_44() { + return ((double) ((response_A << 8) | response_B) * 2.0) / 65536.0; +} + +double ELM327::calculator_4F() { + return (double) (response_A); +} + +double ELM327::calculator_50() { + return (double) (response_A * 10.0); +} + +double ELM327::calculator_53() { + return (double) ((response_A << 8) | response_B) / 200; +} + +double ELM327::calculator_54() { + return (double) ((int16_t)((response_A << 8) | response_B)); +} + +double ELM327::calculator_55() { + return ((double) response_A * (100.0 / 128.0)) - 100.0; +} + +//calc 23 +double ELM327::calculator_59() { + return (double) ((response_A << 8) | response_B) * 10; +} + +double ELM327::calculator_5D() { + return (double) (((response_A << 8) | response_B) / 128) - 210; +} + +double ELM327::calculator_5E() { + return (double) ((response_A << 8) | response_B) / 20; +} + +double ELM327::calculator_61() { + return (double) response_A - 125; } diff --git a/src/ELMduino.h b/src/ELMduino.h index 725b452..6060b83 100644 --- a/src/ELMduino.h +++ b/src/ELMduino.h @@ -1,9 +1,6 @@ #pragma once #include "Arduino.h" - - - //-------------------------------------------------------------------------------------// // Protocol IDs //-------------------------------------------------------------------------------------// @@ -21,397 +18,455 @@ const char SAE_J1939_29_BIT_250_KBAUD = 'A'; const char USER_1_CAN = 'B'; const char USER_2_CAN = 'C'; - - - //-------------------------------------------------------------------------------------// // PIDs (https://en.wikipedia.org/wiki/OBD-II_PIDs) //-------------------------------------------------------------------------------------// -const uint8_t SERVICE_01 = 1; - - -const uint8_t SUPPORTED_PIDS_1_20 = 0; // 0x00 - bit encoded -const uint8_t MONITOR_STATUS_SINCE_DTC_CLEARED = 1; // 0x01 - bit encoded -const uint8_t FREEZE_DTC = 2; // 0x02 - -const uint8_t FUEL_SYSTEM_STATUS = 3; // 0x03 - bit encoded -const uint8_t ENGINE_LOAD = 4; // 0x04 - % -const uint8_t ENGINE_COOLANT_TEMP = 5; // 0x05 - °C -const uint8_t SHORT_TERM_FUEL_TRIM_BANK_1 = 6; // 0x06 - % -const uint8_t LONG_TERM_FUEL_TRIM_BANK_1 = 7; // 0x07 - % -const uint8_t SHORT_TERM_FUEL_TRIM_BANK_2 = 8; // 0x08 - % -const uint8_t LONG_TERM_FUEL_TRIM_BANK_2 = 9; // 0x09 - % -const uint8_t FUEL_PRESSURE = 10; // 0x0A - kPa -const uint8_t INTAKE_MANIFOLD_ABS_PRESSURE = 11; // 0x0B - kPa -const uint8_t ENGINE_RPM = 12; // 0x0C - rpm -const uint8_t VEHICLE_SPEED = 13; // 0x0D - km/h -const uint8_t TIMING_ADVANCE = 14; // 0x0E - ° before TDC -const uint8_t INTAKE_AIR_TEMP = 15; // 0x0F - °C -const uint8_t MAF_FLOW_RATE = 16; // 0x10 - g/s -const uint8_t THROTTLE_POSITION = 17; // 0x11 - % -const uint8_t COMMANDED_SECONDARY_AIR_STATUS = 18; // 0x12 - bit encoded -const uint8_t OXYGEN_SENSORS_PRESENT_2_BANKS = 19; // 0x13 - bit encoded -const uint8_t OXYGEN_SENSOR_1_A = 20; // 0x14 - V % -const uint8_t OXYGEN_SENSOR_2_A = 21; // 0x15 - V % -const uint8_t OXYGEN_SENSOR_3_A = 22; // 0x16 - V % -const uint8_t OXYGEN_SENSOR_4_A = 23; // 0x17 - V % -const uint8_t OXYGEN_SENSOR_5_A = 24; // 0x18 - V % -const uint8_t OXYGEN_SENSOR_6_A = 25; // 0x19 - V % -const uint8_t OXYGEN_SENSOR_7_A = 26; // 0x1A - V % -const uint8_t OXYGEN_SENSOR_8_A = 27; // 0x1B - V % -const uint8_t OBD_STANDARDS = 28; // 0x1C - bit encoded -const uint8_t OXYGEN_SENSORS_PRESENT_4_BANKS = 29; // 0x1D - bit encoded -const uint8_t AUX_INPUT_STATUS = 30; // 0x1E - bit encoded -const uint8_t RUN_TIME_SINCE_ENGINE_START = 31; // 0x1F - sec - -const uint8_t SUPPORTED_PIDS_21_40 = 32; // 0x20 - bit encoded -const uint8_t DISTANCE_TRAVELED_WITH_MIL_ON = 33; // 0x21 - km -const uint8_t FUEL_RAIL_PRESSURE = 34; // 0x22 - kPa -const uint8_t FUEL_RAIL_GUAGE_PRESSURE = 35; // 0x23 - kPa -const uint8_t OXYGEN_SENSOR_1_B = 36; // 0x24 - ratio V -const uint8_t OXYGEN_SENSOR_2_B = 37; // 0x25 - ratio V -const uint8_t OXYGEN_SENSOR_3_B = 38; // 0x26 - ratio V -const uint8_t OXYGEN_SENSOR_4_B = 39; // 0x27 - ratio V -const uint8_t OXYGEN_SENSOR_5_B = 40; // 0x28 - ratio V -const uint8_t OXYGEN_SENSOR_6_B = 41; // 0x29 - ratio V -const uint8_t OXYGEN_SENSOR_7_B = 42; // 0x2A - ratio V -const uint8_t OXYGEN_SENSOR_8_B = 43; // 0x2B - ratio V -const uint8_t COMMANDED_EGR = 44; // 0x2C - % -const uint8_t EGR_ERROR = 45; // 0x2D - % -const uint8_t COMMANDED_EVAPORATIVE_PURGE = 46; // 0x2E - % -const uint8_t FUEL_TANK_LEVEL_INPUT = 47; // 0x2F - % -const uint8_t WARM_UPS_SINCE_CODES_CLEARED = 48; // 0x30 - count -const uint8_t DIST_TRAV_SINCE_CODES_CLEARED = 49; // 0x31 - km -const uint8_t EVAP_SYSTEM_VAPOR_PRESSURE = 50; // 0x32 - Pa -const uint8_t ABS_BAROMETRIC_PRESSURE = 51; // 0x33 - kPa -const uint8_t OXYGEN_SENSOR_1_C = 52; // 0x34 - ratio mA -const uint8_t OXYGEN_SENSOR_2_C = 53; // 0x35 - ratio mA -const uint8_t OXYGEN_SENSOR_3_C = 54; // 0x36 - ratio mA -const uint8_t OXYGEN_SENSOR_4_C = 55; // 0x37 - ratio mA -const uint8_t OXYGEN_SENSOR_5_C = 56; // 0x38 - ratio mA -const uint8_t OXYGEN_SENSOR_6_C = 57; // 0x39 - ratio mA -const uint8_t OXYGEN_SENSOR_7_C = 58; // 0x3A - ratio mA -const uint8_t OXYGEN_SENSOR_8_C = 59; // 0x3B - ratio mA -const uint8_t CATALYST_TEMP_BANK_1_SENSOR_1 = 60; // 0x3C - °C -const uint8_t CATALYST_TEMP_BANK_2_SENSOR_1 = 61; // 0x3D - °C -const uint8_t CATALYST_TEMP_BANK_1_SENSOR_2 = 62; // 0x3E - °C -const uint8_t CATALYST_TEMP_BANK_2_SENSOR_2 = 63; // 0x3F - °C - -const uint8_t SUPPORTED_PIDS_41_60 = 64; // 0x40 - bit encoded -const uint8_t MONITOR_STATUS_THIS_DRIVE_CYCLE = 65; // 0x41 - bit encoded -const uint8_t CONTROL_MODULE_VOLTAGE = 66; // 0x42 - V -const uint8_t ABS_LOAD_VALUE = 67; // 0x43 - % -const uint8_t FUEL_AIR_COMMANDED_EQUIV_RATIO = 68; // 0x44 - ratio -const uint8_t RELATIVE_THROTTLE_POSITION = 69; // 0x45 - % -const uint8_t AMBIENT_AIR_TEMP = 70; // 0x46 - °C -const uint8_t ABS_THROTTLE_POSITION_B = 71; // 0x47 - % -const uint8_t ABS_THROTTLE_POSITION_C = 72; // 0x48 - % -const uint8_t ABS_THROTTLE_POSITION_D = 73; // 0x49 - % -const uint8_t ABS_THROTTLE_POSITION_E = 74; // 0x4A - % -const uint8_t ABS_THROTTLE_POSITION_F = 75; // 0x4B - % -const uint8_t COMMANDED_THROTTLE_ACTUATOR = 76; // 0x4C - % -const uint8_t TIME_RUN_WITH_MIL_ON = 77; // 0x4D - min -const uint8_t TIME_SINCE_CODES_CLEARED = 78; // 0x4E - min -const uint8_t MAX_VALUES_EQUIV_V_I_PRESSURE = 79; // 0x4F - ratio V mA kPa -const uint8_t MAX_MAF_RATE = 80; // 0x50 - g/s -const uint8_t FUEL_TYPE = 81; // 0x51 - ref table -const uint8_t ETHONOL_FUEL_PERCENT = 82; // 0x52 - % -const uint8_t ABS_EVAP_SYS_VAPOR_PRESSURE = 83; // 0x53 - kPa -const uint8_t EVAP_SYS_VAPOR_PRESSURE = 84; // 0x54 - Pa -const uint8_t SHORT_TERM_SEC_OXY_SENS_TRIM_1_3 = 85; // 0x55 - % -const uint8_t LONG_TERM_SEC_OXY_SENS_TRIM_1_3 = 86; // 0x56 - % -const uint8_t SHORT_TERM_SEC_OXY_SENS_TRIM_2_4 = 87; // 0x57 - % -const uint8_t LONG_TERM_SEC_OXY_SENS_TRIM_2_4 = 88; // 0x58 - % -const uint8_t FUEL_RAIL_ABS_PRESSURE = 89; // 0x59 - kPa -const uint8_t RELATIVE_ACCELERATOR_PEDAL_POS = 90; // 0x5A - % -const uint8_t HYBRID_BATTERY_REMAINING_LIFE = 91; // 0x5B - % -const uint8_t ENGINE_OIL_TEMP = 92; // 0x5C - °C -const uint8_t FUEL_INJECTION_TIMING = 93; // 0x5D - ° -const uint8_t ENGINE_FUEL_RATE = 94; // 0x5E - L/h -const uint8_t EMISSION_REQUIREMENTS = 95; // 0x5F - bit encoded - -const uint8_t SUPPORTED_PIDS_61_80 = 96; // 0x60 - bit encoded -const uint8_t DEMANDED_ENGINE_PERCENT_TORQUE = 97; // 0x61 - % -const uint8_t ACTUAL_ENGINE_TORQUE = 98; // 0x62 - % -const uint8_t ENGINE_REFERENCE_TORQUE = 99; // 0x63 - Nm -const uint8_t ENGINE_PERCENT_TORQUE_DATA = 100; // 0x64 - % -const uint8_t AUX_INPUT_OUTPUT_SUPPORTED = 101; // 0x65 - bit encoded - - -const uint8_t SERVICE_02 = 2; - - - +constexpr uint8_t SERVICE_01 = 1; +constexpr uint8_t SERVICE_02 = 2; +constexpr uint8_t SERVICE_03 = 3; +constexpr uint8_t PID_INTERVAL_OFFSET = 0x20; + + +constexpr uint8_t SUPPORTED_PIDS_1_20 = 0; // 0x00 - bit encoded +constexpr uint8_t MONITOR_STATUS_SINCE_DTC_CLEARED = 1; // 0x01 - bit encoded +constexpr uint8_t FREEZE_DTC = 2; // 0x02 - +constexpr uint8_t FUEL_SYSTEM_STATUS = 3; // 0x03 - bit encoded +constexpr uint8_t ENGINE_LOAD = 4; // 0x04 - % +constexpr uint8_t ENGINE_COOLANT_TEMP = 5; // 0x05 - °C +constexpr uint8_t SHORT_TERM_FUEL_TRIM_BANK_1 = 6; // 0x06 - % +constexpr uint8_t LONG_TERM_FUEL_TRIM_BANK_1 = 7; // 0x07 - % +constexpr uint8_t SHORT_TERM_FUEL_TRIM_BANK_2 = 8; // 0x08 - % +constexpr uint8_t LONG_TERM_FUEL_TRIM_BANK_2 = 9; // 0x09 - % +constexpr uint8_t FUEL_PRESSURE = 10; // 0x0A - kPa +constexpr uint8_t INTAKE_MANIFOLD_ABS_PRESSURE = 11; // 0x0B - kPa +constexpr uint8_t ENGINE_RPM = 12; // 0x0C - rpm +constexpr uint8_t VEHICLE_SPEED = 13; // 0x0D - km/h +constexpr uint8_t TIMING_ADVANCE = 14; // 0x0E - ° before TDC +constexpr uint8_t INTAKE_AIR_TEMP = 15; // 0x0F - °C +constexpr uint8_t MAF_FLOW_RATE = 16; // 0x10 - g/s +constexpr uint8_t THROTTLE_POSITION = 17; // 0x11 - % +constexpr uint8_t COMMANDED_SECONDARY_AIR_STATUS = 18; // 0x12 - bit encoded +constexpr uint8_t OXYGEN_SENSORS_PRESENT_2_BANKS = 19; // 0x13 - bit encoded +constexpr uint8_t OXYGEN_SENSOR_1_A = 20; // 0x14 - V % +constexpr uint8_t OXYGEN_SENSOR_2_A = 21; // 0x15 - V % +constexpr uint8_t OXYGEN_SENSOR_3_A = 22; // 0x16 - V % +constexpr uint8_t OXYGEN_SENSOR_4_A = 23; // 0x17 - V % +constexpr uint8_t OXYGEN_SENSOR_5_A = 24; // 0x18 - V % +constexpr uint8_t OXYGEN_SENSOR_6_A = 25; // 0x19 - V % +constexpr uint8_t OXYGEN_SENSOR_7_A = 26; // 0x1A - V % +constexpr uint8_t OXYGEN_SENSOR_8_A = 27; // 0x1B - V % +constexpr uint8_t OBD_STANDARDS = 28; // 0x1C - bit encoded +constexpr uint8_t OXYGEN_SENSORS_PRESENT_4_BANKS = 29; // 0x1D - bit encoded +constexpr uint8_t AUX_INPUT_STATUS = 30; // 0x1E - bit encoded +constexpr uint8_t RUN_TIME_SINCE_ENGINE_START = 31; // 0x1F - sec + +constexpr uint8_t SUPPORTED_PIDS_21_40 = 32; // 0x20 - bit encoded +constexpr uint8_t DISTANCE_TRAVELED_WITH_MIL_ON = 33; // 0x21 - km +constexpr uint8_t FUEL_RAIL_PRESSURE = 34; // 0x22 - kPa +constexpr uint8_t FUEL_RAIL_GUAGE_PRESSURE = 35; // 0x23 - kPa +constexpr uint8_t OXYGEN_SENSOR_1_B = 36; // 0x24 - ratio V +constexpr uint8_t OXYGEN_SENSOR_2_B = 37; // 0x25 - ratio V +constexpr uint8_t OXYGEN_SENSOR_3_B = 38; // 0x26 - ratio V +constexpr uint8_t OXYGEN_SENSOR_4_B = 39; // 0x27 - ratio V +constexpr uint8_t OXYGEN_SENSOR_5_B = 40; // 0x28 - ratio V +constexpr uint8_t OXYGEN_SENSOR_6_B = 41; // 0x29 - ratio V +constexpr uint8_t OXYGEN_SENSOR_7_B = 42; // 0x2A - ratio V +constexpr uint8_t OXYGEN_SENSOR_8_B = 43; // 0x2B - ratio V +constexpr uint8_t COMMANDED_EGR = 44; // 0x2C - % +constexpr uint8_t EGR_ERROR = 45; // 0x2D - % +constexpr uint8_t COMMANDED_EVAPORATIVE_PURGE = 46; // 0x2E - % +constexpr uint8_t FUEL_TANK_LEVEL_INPUT = 47; // 0x2F - % +constexpr uint8_t WARM_UPS_SINCE_CODES_CLEARED = 48; // 0x30 - count +constexpr uint8_t DIST_TRAV_SINCE_CODES_CLEARED = 49; // 0x31 - km +constexpr uint8_t EVAP_SYSTEM_VAPOR_PRESSURE = 50; // 0x32 - Pa +constexpr uint8_t ABS_BAROMETRIC_PRESSURE = 51; // 0x33 - kPa +constexpr uint8_t OXYGEN_SENSOR_1_C = 52; // 0x34 - ratio mA +constexpr uint8_t OXYGEN_SENSOR_2_C = 53; // 0x35 - ratio mA +constexpr uint8_t OXYGEN_SENSOR_3_C = 54; // 0x36 - ratio mA +constexpr uint8_t OXYGEN_SENSOR_4_C = 55; // 0x37 - ratio mA +constexpr uint8_t OXYGEN_SENSOR_5_C = 56; // 0x38 - ratio mA +constexpr uint8_t OXYGEN_SENSOR_6_C = 57; // 0x39 - ratio mA +constexpr uint8_t OXYGEN_SENSOR_7_C = 58; // 0x3A - ratio mA +constexpr uint8_t OXYGEN_SENSOR_8_C = 59; // 0x3B - ratio mA +constexpr uint8_t CATALYST_TEMP_BANK_1_SENSOR_1 = 60; // 0x3C - °C +constexpr uint8_t CATALYST_TEMP_BANK_2_SENSOR_1 = 61; // 0x3D - °C +constexpr uint8_t CATALYST_TEMP_BANK_1_SENSOR_2 = 62; // 0x3E - °C +constexpr uint8_t CATALYST_TEMP_BANK_2_SENSOR_2 = 63; // 0x3F - °C + +constexpr uint8_t SUPPORTED_PIDS_41_60 = 64; // 0x40 - bit encoded +constexpr uint8_t MONITOR_STATUS_THIS_DRIVE_CYCLE = 65; // 0x41 - bit encoded +constexpr uint8_t CONTROL_MODULE_VOLTAGE = 66; // 0x42 - V +constexpr uint8_t ABS_LOAD_VALUE = 67; // 0x43 - % +constexpr uint8_t FUEL_AIR_COMMANDED_EQUIV_RATIO = 68; // 0x44 - ratio +constexpr uint8_t RELATIVE_THROTTLE_POSITION = 69; // 0x45 - % +constexpr uint8_t AMBIENT_AIR_TEMP = 70; // 0x46 - °C +constexpr uint8_t ABS_THROTTLE_POSITION_B = 71; // 0x47 - % +constexpr uint8_t ABS_THROTTLE_POSITION_C = 72; // 0x48 - % +constexpr uint8_t ABS_THROTTLE_POSITION_D = 73; // 0x49 - % +constexpr uint8_t ABS_THROTTLE_POSITION_E = 74; // 0x4A - % +constexpr uint8_t ABS_THROTTLE_POSITION_F = 75; // 0x4B - % +constexpr uint8_t COMMANDED_THROTTLE_ACTUATOR = 76; // 0x4C - % +constexpr uint8_t TIME_RUN_WITH_MIL_ON = 77; // 0x4D - min +constexpr uint8_t TIME_SINCE_CODES_CLEARED = 78; // 0x4E - min +constexpr uint8_t MAX_VALUES_EQUIV_V_I_PRESSURE = 79; // 0x4F - ratio V mA kPa +constexpr uint8_t MAX_MAF_RATE = 80; // 0x50 - g/s +constexpr uint8_t FUEL_TYPE = 81; // 0x51 - ref table +constexpr uint8_t ETHANOL_FUEL_PERCENT = 82; // 0x52 - % +constexpr uint8_t ABS_EVAP_SYS_VAPOR_PRESSURE = 83; // 0x53 - kPa +constexpr uint8_t EVAP_SYS_VAPOR_PRESSURE = 84; // 0x54 - Pa +constexpr uint8_t SHORT_TERM_SEC_OXY_SENS_TRIM_1_3 = 85; // 0x55 - % +constexpr uint8_t LONG_TERM_SEC_OXY_SENS_TRIM_1_3 = 86; // 0x56 - % +constexpr uint8_t SHORT_TERM_SEC_OXY_SENS_TRIM_2_4 = 87; // 0x57 - % +constexpr uint8_t LONG_TERM_SEC_OXY_SENS_TRIM_2_4 = 88; // 0x58 - % +constexpr uint8_t FUEL_RAIL_ABS_PRESSURE = 89; // 0x59 - kPa +constexpr uint8_t RELATIVE_ACCELERATOR_PEDAL_POS = 90; // 0x5A - % +constexpr uint8_t HYBRID_BATTERY_REMAINING_LIFE = 91; // 0x5B - % +constexpr uint8_t ENGINE_OIL_TEMP = 92; // 0x5C - °C +constexpr uint8_t FUEL_INJECTION_TIMING = 93; // 0x5D - ° +constexpr uint8_t ENGINE_FUEL_RATE = 94; // 0x5E - L/h +constexpr uint8_t EMISSION_REQUIREMENTS = 95; // 0x5F - bit encoded + +constexpr uint8_t SUPPORTED_PIDS_61_80 = 96; // 0x60 - bit encoded +constexpr uint8_t DEMANDED_ENGINE_PERCENT_TORQUE = 97; // 0x61 - % +constexpr uint8_t ACTUAL_ENGINE_TORQUE = 98; // 0x62 - % +constexpr uint8_t ENGINE_REFERENCE_TORQUE = 99; // 0x63 - Nm +constexpr uint8_t ENGINE_PERCENT_TORQUE_DATA = 100; // 0x64 - % +constexpr uint8_t AUX_INPUT_OUTPUT_SUPPORTED = 101; // 0x65 - bit encoded //-------------------------------------------------------------------------------------// // AT commands (https://www.sparkfun.com/datasheets/Widgets/ELM327_AT_Commands.pdf) //-------------------------------------------------------------------------------------// -const char * const DISP_DEVICE_DESCRIPT = "AT @1"; // General -const char * const DISP_DEVICE_ID = "AT @2"; // General -const char * const STORE_DEVICE_ID = "AT @3 %s"; // General -const char * const REPEAT_LAST_COMMAND = "AT \r"; // General -const char * const ALLOW_LONG_MESSAGES = "AT AL"; // General -const char * const AUTOMATIC_RECEIVE = "AT AR"; // OBD -const char * const ADAPTIVE_TIMING_OFF = "AT AT0"; // OBD -const char * const ADAPTIVE_TIMING_AUTO_1 = "AT AT1"; // OBD -const char * const ADAPTIVE_TIMING_AUTO_2 = "AT AT2"; // OBD -const char * const DUMP_BUFFER = "AT BD"; // OBD -const char * const BYPASS_INIT_SEQUENCE = "AT BI"; // OBD -const char * const TRY_BAUD_DIVISOR = "AT BRD %s"; // General -const char * const SET_HANDSHAKE_TIMEOUT = "AT BRT %s"; // General -const char * const CAN_AUTO_FORMAT_OFF = "AT CAF0"; // CAN -const char * const CAN_AUTO_FORMAT_ON = "AT CAF1"; // CAN -const char * const CAN_EXTENDED_ADDRESS_OFF = "AT CEA"; // CAN -const char * const USE_CAN_EXTENDED_ADDRESS = "AT CEA %s"; // CAN -const char * const SET_ID_FILTER = "AT CF %s"; // CAN -const char * const CAN_FLOW_CONTROL_OFF = "AT CFC0"; // CAN -const char * const CAN_FLOW_CONTROL_ON = "AT CFC1"; // CAN -const char * const SET_ID_MASK = "AT CM %s"; // CAN -const char * const SET_CAN_PRIORITY = "AT CP %s"; // CAN -const char * const SHOW_CAN_STATUS = "AT CS"; // CAN -const char * const CAN_SILENT_MODE_OFF = "AT CSM0"; // CAN -const char * const CAN_SILENT_MODE_ON = "AT CSM1"; // CAN -const char * const CALIBRATE_VOLTAGE_CUSTOM = "AT CV %s"; // Volts -const char * const RESTORE_CV_TO_FACTORY = "AT CV 0000"; // Volts -const char * const SET_ALL_TO_DEFAULTS = "AT D"; // General -const char * const DISP_DLC_OFF = "AT D0"; // CAN -const char * const DISP_DLC_ON = "AT D1"; // CAN -const char * const MONITOR_FOR_DM1_MESSAGES = "AT DM1"; // J1939 -const char * const DISP_CURRENT_PROTOCOL = "AT DP"; // OBD -const char * const DISP_CURRENT_PROTOCOL_NUM = "AT DPN"; // OBD -const char * const ECHO_OFF = "AT E0"; // General -const char * const ECHO_ON = "AT E1"; // General +const char * const DISP_DEVICE_DESCRIPT = "AT @1"; // General +const char * const DISP_DEVICE_ID = "AT @2"; // General +const char * const STORE_DEVICE_ID = "AT @3 %s"; // General +const char * const REPEAT_LAST_COMMAND = "AT \r"; // General +const char * const ALLOW_LONG_MESSAGES = "AT AL"; // General +const char * const AUTOMATIC_RECEIVE = "AT AR"; // OBD +const char * const ADAPTIVE_TIMING_OFF = "AT AT0"; // OBD +const char * const ADAPTIVE_TIMING_AUTO_1 = "AT AT1"; // OBD +const char * const ADAPTIVE_TIMING_AUTO_2 = "AT AT2"; // OBD +const char * const DUMP_BUFFER = "AT BD"; // OBD +const char * const BYPASS_INIT_SEQUENCE = "AT BI"; // OBD +const char * const TRY_BAUD_DIVISOR = "AT BRD %02d"; // General +const char * const SET_HANDSHAKE_TIMEOUT = "AT BRT %02d"; // General +const char * const CAN_AUTO_FORMAT_OFF = "AT CAF0"; // CAN +const char * const CAN_AUTO_FORMAT_ON = "AT CAF1"; // CAN +const char * const CAN_EXTENDED_ADDRESS_OFF = "AT CEA"; // CAN +const char * const USE_CAN_EXTENDED_ADDRESS = "AT CEA %02d"; // CAN +const char * const SET_ID_FILTER = "AT CF %s"; // CAN +const char * const CAN_FLOW_CONTROL_OFF = "AT CFC0"; // CAN +const char * const CAN_FLOW_CONTROL_ON = "AT CFC1"; // CAN +const char * const SET_ID_MASK = "AT CM %s"; // CAN +const char * const SET_CAN_PRIORITY = "AT CP %02d"; // CAN +const char * const SHOW_CAN_STATUS = "AT CS"; // CAN +const char * const CAN_SILENT_MODE_OFF = "AT CSM0"; // CAN +const char * const CAN_SILENT_MODE_ON = "AT CSM1"; // CAN +const char * const CALIBRATE_VOLTAGE_CUSTOM = "AT CV %04d"; // Volts +const char * const RESTORE_CV_TO_FACTORY = "AT CV 0000"; // Volts +const char * const SET_ALL_TO_DEFAULTS = "AT D"; // General +const char * const DISP_DLC_OFF = "AT D0"; // CAN +const char * const DISP_DLC_ON = "AT D1"; // CAN +const char * const MONITOR_FOR_DM1_MESSAGES = "AT DM1"; // J1939 +const char * const DISP_CURRENT_PROTOCOL = "AT DP"; // OBD +const char * const DISP_CURRENT_PROTOCOL_NUM = "AT DPN"; // OBD +const char * const ECHO_OFF = "AT E0"; // General +const char * const ECHO_ON = "AT E1"; // General const char * const FLOW_CONTROL_SET_DATA_TO = "AT FC SD %s"; // CAN const char * const FLOW_CONTROL_SET_HEAD_TO = "AT FC SH %s"; // CAN const char * const FLOW_CONTROL_SET_MODE_TO = "AT FC SM %c"; // CAN -const char * const FORGE_EVENTS = "AT FE"; // General -const char * const PERFORM_FAST_INIT = "AT FI"; // ISO -const char * const HEADERS_OFF = "AT H0"; // OBD -const char * const HEADERS_ON = "AT H1"; // OBD -const char * const DISP_ID = "AT I"; // General -const char * const SET_ISO_BAUD_10400 = "AT IB 10"; // ISO -const char * const SET_ISO_BAUD_4800 = "AT IB 48"; // ISO -const char * const SET_ISO_BAUD_9600 = "AT IB 96"; // ISO -const char * const IFR_VAL_FROM_HEADER = "AT IFR H"; // J1850 -const char * const IFR_VAL_FROM_SOURCE = "AT IFR S"; // J1850 -const char * const IFRS_OFF = "AT IFR0"; // J1850 -const char * const IFRS_AUTO = "AT IFR1"; // J1850 -const char * const IFRS_ON = "AT IFR2"; // J1850 -const char * const IREAD_IGNMON_INPUT_LEVEL = "AT IGN"; // Other -const char * const SET_ISO_SLOW_INIT_ADDRESS = "AT IIA %s"; // ISO -const char * const USE_J1939_ELM_DATA_FORMAT = "AT JE"; // J1850 -const char * const J1939_HEAD_FORMAT_OFF = "AT JHF0"; // J1850 -const char * const J1939_HEAD_FORMAT_ON = "AT JHF1"; // J1850 -const char * const USE_J1939_SAE_DATA_FORMAT = "AT JS"; // J1850 -const char * const SET_J1939_TIMER_X_TO_1X = "AT JTM1"; // J1850 -const char * const SET_J1939_TIMER_X_TO_5X = "AT JTM5"; // J1850 -const char * const DISP_KEY_WORDS = "AT KW"; // ISO -const char * const KEY_WORD_CHECKING_OFF = "AT KW0"; // ISO -const char * const KEY_WORD_CHECKING_ON = "AT KW1"; // ISO -const char * const LINEFEEDS_OFF = "AT L0"; // General -const char * const LINEFEEDS_ON = "AT L1"; // General -const char * const LOW_POWER_MODE = "AT LP"; // General -const char * const MEMORY_OFF = "AT M0"; // General -const char * const MEMORY_ON = "AT M1"; // General -const char * const MONITOR_ALL = "AT MA"; // OBD -const char * const MONITOR_FOR_PGN = "AT MP %s"; // J1939 -const char * const MONITOR_FOR_RECEIVER = "AT MR %s"; // OBD -const char * const MONITOR_FOR_TRANSMITTER = "AT MT %s"; // OBD -const char * const NORMAL_LENGTH_MESSAGES = "AT NL"; // OBD -const char * const SET_PROTO_OPTIONS_AND_BAUD = "AT PB %s"; // OBD -const char * const PROTOCOL_CLOSE = "AT PC"; // OBD -const char * const ALL_PROG_PARAMS_OFF = "AT PP FF OFF"; // PPs -const char * const ALL_PROG_PARAMS_ON = "AT PP FF ON"; // PPs -const char * const SET_PROG_PARAM_OFF = "AT PP %s OFF"; // PPs -const char * const SET_PROG_PARAM_ON = "AT PP %s ON"; // PPs +const char * const FORGE_EVENTS = "AT FE"; // General +const char * const PERFORM_FAST_INIT = "AT FI"; // ISO +const char * const HEADERS_OFF = "AT H0"; // OBD +const char * const HEADERS_ON = "AT H1"; // OBD +const char * const DISP_ID = "AT I"; // General +const char * const SET_ISO_BAUD_10400 = "AT IB 10"; // ISO +const char * const SET_ISO_BAUD_4800 = "AT IB 48"; // ISO +const char * const SET_ISO_BAUD_9600 = "AT IB 96"; // ISO +const char * const IFR_VAL_FROM_HEADER = "AT IFR H"; // J1850 +const char * const IFR_VAL_FROM_SOURCE = "AT IFR S"; // J1850 +const char * const IFRS_OFF = "AT IFR0"; // J1850 +const char * const IFRS_AUTO = "AT IFR1"; // J1850 +const char * const IFRS_ON = "AT IFR2"; // J1850 +const char * const IREAD_IGNMON_INPUT_LEVEL = "AT IGN"; // Other +const char * const SET_ISO_SLOW_INIT_ADDRESS = "AT IIA %02d"; // ISO +const char * const USE_J1939_ELM_DATA_FORMAT = "AT JE"; // J1850 +const char * const J1939_HEAD_FORMAT_OFF = "AT JHF0"; // J1850 +const char * const J1939_HEAD_FORMAT_ON = "AT JHF1"; // J1850 +const char * const USE_J1939_SAE_DATA_FORMAT = "AT JS"; // J1850 +const char * const SET_J1939_TIMER_X_TO_1X = "AT JTM1"; // J1850 +const char * const SET_J1939_TIMER_X_TO_5X = "AT JTM5"; // J1850 +const char * const DISP_KEY_WORDS = "AT KW"; // ISO +const char * const KEY_WORD_CHECKING_OFF = "AT KW0"; // ISO +const char * const KEY_WORD_CHECKING_ON = "AT KW1"; // ISO +const char * const LINEFEEDS_OFF = "AT L0"; // General +const char * const LINEFEEDS_ON = "AT L1"; // General +const char * const LOW_POWER_MODE = "AT LP"; // General +const char * const MEMORY_OFF = "AT M0"; // General +const char * const MEMORY_ON = "AT M1"; // General +const char * const MONITOR_ALL = "AT MA"; // OBD +const char * const MONITOR_FOR_PGN = "AT MP %s"; // J1939 +const char * const MONITOR_FOR_RECEIVER = "AT MR %02d"; // OBD +const char * const MONITOR_FOR_TRANSMITTER = "AT MT %02d"; // OBD +const char * const NORMAL_LENGTH_MESSAGES = "AT NL"; // OBD +const char * const SET_PROTO_OPTIONS_AND_BAUD = "AT PB %s"; // OBD +const char * const PROTOCOL_CLOSE = "AT PC"; // OBD +const char * const ALL_PROG_PARAMS_OFF = "AT PP FF OFF";// PPs +const char * const ALL_PROG_PARAMS_ON = "AT PP FF ON"; // PPs +const char * const SET_PROG_PARAM_OFF = "AT PP %s OFF";// PPs +const char * const SET_PROG_PARAM_ON = "AT PP %s ON"; // PPs const char * const SET_PROG_PARAM_VAL = "AT PP %s SV %s"; // PPs -const char * const DISP_PP_SUMMARY = "AT PPS"; // PPs -const char * const RESPONSES_OFF = "AT R0"; // OBD -const char * const RESPONSES_ON = "AT R1"; // OBD -const char * const SET_RECEIVE_ADDRESS_TO = "AT RA %s"; // OBD -const char * const READ_STORED_DATA = "AT RD"; // General -const char * const SEND_RTR_MESSAGE = "AT RTR"; // CAN -const char * const READ_VOLTAGE = "AT RV"; // Volts -const char * const PRINTING_SPACES_OFF = "AT S0"; // OBD -const char * const PRINTING_SPACES_ON = "AT S1"; // OBD -const char * const STORE_DATA_BYTE = "AT SD "; // General -const char * const SET_HEADER = "AT SH %s"; // OBD -const char * const PERFORM_SLOW_INIT = "AT SI"; // ISO -const char * const SET_PROTOCOL_TO_AUTO_H_SAVE = "AT SP A%c"; // OBD -const char * const SET_PROTOCOL_TO_H_SAVE = "AT SP %c"; // OBD -const char * const SET_PROTOCOL_TO_AUTO_SAVE = "AT SP 00"; // OBD -const char * const SET_REC_ADDRESS = "AT SR %s"; // OBD -const char * const SET_STANDARD_SEARCH_ORDER = "AT SS"; // OBD -const char * const SET_TIMEOUT_TO_H_X_4MS = "AT ST %s"; // OBD -const char * const SET_WAKEUP_TO_H_X_20MS = "AT SW %s"; // ISO -const char * const SET_TESTER_ADDRESS_TO = "AT TA %s"; // OBD -const char * const TRY_PROT_H_AUTO_SEARCH = "AT TP A%c"; // OBD -const char * const TRY_PROT_H = "AT TP %c"; // OBD -const char * const VARIABLE_DLC_OFF = "AT V0"; // CAN -const char * const VARIABLE_DLC_ON = "AT V1"; // CAN -const char * const SET_WAKEUP_MESSAGE = "AT WM"; // ISO -const char * const WARM_START = "AT WS"; // General -const char * const RESET_ALL = "AT Z"; // General - - - +const char * const DISP_PP_SUMMARY = "AT PPS"; // PPs +const char * const RESPONSES_OFF = "AT R0"; // OBD +const char * const RESPONSES_ON = "AT R1"; // OBD +const char * const SET_RECEIVE_ADDRESS_TO = "AT RA %02d"; // OBD +const char * const READ_STORED_DATA = "AT RD"; // General +const char * const SEND_RTR_MESSAGE = "AT RTR"; // CAN +const char * const READ_VOLTAGE = "AT RV"; // Volts +const char * const PRINTING_SPACES_OFF = "AT S0"; // OBD +const char * const PRINTING_SPACES_ON = "AT S1"; // OBD +const char * const STORE_DATA_BYTE = "AT SD "; // General +const char * const SET_HEADER = "AT SH %s"; // OBD +const char * const PERFORM_SLOW_INIT = "AT SI"; // ISO +const char * const SET_PROTOCOL_TO_AUTO_H_SAVE = "AT SP A%c"; // OBD +const char * const SET_PROTOCOL_TO_H_SAVE = "AT SP %c"; // OBD +const char * const SET_PROTOCOL_TO_AUTO_SAVE = "AT SP 00"; // OBD +const char * const SET_REC_ADDRESS = "AT SR %02d"; // OBD +const char * const SET_STANDARD_SEARCH_ORDER = "AT SS"; // OBD +const char * const SET_TIMEOUT_TO_H_X_4MS = "AT ST %02d"; // OBD +const char * const SET_WAKEUP_TO_H_X_20MS = "AT SW %02d"; // ISO +const char * const SET_TESTER_ADDRESS_TO = "AT TA %02d"; // OBD +const char * const TRY_PROT_H_AUTO_SEARCH = "AT TP A%c"; // OBD +const char * const TRY_PROT_H = "AT TP %c"; // OBD +const char * const VARIABLE_DLC_OFF = "AT V0"; // CAN +const char * const VARIABLE_DLC_ON = "AT V1"; // CAN +const char * const SET_WAKEUP_MESSAGE = "AT WM"; // ISO +const char * const WARM_START = "AT WS"; // General +const char * const RESET_ALL = "AT Z"; // General //-------------------------------------------------------------------------------------// // Class constants //-------------------------------------------------------------------------------------// -const float KPH_MPH_CONVERT = 0.6213711922; -const float RPM_CONVERT = 0.25; -const int8_t QUERY_LEN = 7; -const int8_t ELM_SUCCESS = 0; -const int8_t ELM_NO_RESPONSE = 1; -const int8_t ELM_BUFFER_OVERFLOW = 2; -const int8_t ELM_GARBAGE = 3; -const int8_t ELM_UNABLE_TO_CONNECT = 4; -const int8_t ELM_NO_DATA = 5; -const int8_t ELM_STOPPED = 6; -const int8_t ELM_TIMEOUT = 7; -const int8_t ELM_GENERAL_ERROR = -1; - - +constexpr float KPH_MPH_CONVERT = 0.6213711922; +constexpr int8_t QUERY_LEN = 9; +constexpr int8_t ELM_SUCCESS = 0; +constexpr int8_t ELM_NO_RESPONSE = 1; +constexpr int8_t ELM_BUFFER_OVERFLOW = 2; +constexpr int8_t ELM_GARBAGE = 3; +constexpr int8_t ELM_UNABLE_TO_CONNECT = 4; +constexpr int8_t ELM_NO_DATA = 5; +constexpr int8_t ELM_STOPPED = 6; +constexpr int8_t ELM_TIMEOUT = 7; +constexpr int8_t ELM_GETTING_MSG = 8; +constexpr int8_t ELM_MSG_RXD = 9; +constexpr int8_t ELM_GENERAL_ERROR = -1; +constexpr uint8_t DTC_CODE_LEN = 6; +constexpr uint8_t DTC_MAX_CODES = 16; + +const char * const RESPONSE_OK = "OK"; +const char * const RESPONSE_UNABLE_TO_CONNECT = "UNABLETOCONNECT"; +const char * const RESPONSE_NO_DATA = "NODATA"; +const char * const RESPONSE_STOPPED = "STOPPED"; +const char * const RESPONSE_ERROR = "ERROR"; + +// Non-blocking (NB) command states +typedef enum { SEND_COMMAND, + WAITING_RESP, + RESPONSE_RECEIVED, + DECODED_OK, + ERROR } obd_cmd_states; + +// Pointers to existing response bytes, to be used for new calculators without breaking +// backward compatability with code that may use the above response bytes. +static byte response_A; +static byte response_B; +static byte response_C; +static byte response_D; +static byte response_E; +static byte response_F; +static byte response_G; +static byte response_H; class ELM327 { public: - Stream* elm_port; - - bool connected = false; - bool debugMode; - char* payload; - uint16_t PAYLOAD_LEN; - int8_t status = ELM_GENERAL_ERROR; - uint64_t response; - uint16_t recBytes; - uint8_t numPayChars; - uint16_t timeout_ms; - byte responseByte_0; - byte responseByte_1; - byte responseByte_2; - byte responseByte_3; - byte responseByte_4; - byte responseByte_5; - byte responseByte_6; - byte responseByte_7; - - - - - bool begin(Stream& stream, const bool& debug = false, const uint16_t& timeout = 1000, const char& protocol = '0', const uint16_t& payloadLen = 40); - bool initializeELM(const char& protocol='0'); - void flushInputBuff(); - uint64_t findResponse(); - bool queryPID(uint8_t service, uint16_t pid); - bool queryPID(char queryStr[]); - int8_t sendCommand(const char *cmd); - bool timeout(); - - - uint32_t supportedPIDs_1_20(); - - uint32_t monitorStatus(); - uint16_t freezeDTC(); - uint16_t fuelSystemStatus(); - float engineLoad(); - float engineCoolantTemp(); - float shortTermFuelTrimBank_1(); - float longTermFuelTrimBank_1(); - float shortTermFuelTrimBank_2(); - float longTermFuelTrimBank_2(); - float fuelPressure(); - uint8_t manifoldPressure(); - float rpm(); - int32_t kph(); - float mph(); - float timingAdvance(); - float intakeAirTemp(); - float mafRate(); - float throttle(); - uint8_t commandedSecAirStatus(); - uint8_t oxygenSensorsPresent_2banks(); - uint8_t obdStandards(); - uint8_t oxygenSensorsPresent_4banks(); - bool auxInputStatus(); - uint16_t runTime(); - - - uint32_t supportedPIDs_21_40(); - - uint16_t distTravelWithMIL(); - float fuelRailPressure(); - float fuelRailGuagePressure(); - float commandedEGR(); - float egrError(); - float commandedEvapPurge(); - float fuelLevel(); - uint8_t warmUpsSinceCodesCleared(); - uint16_t distSinceCodesCleared(); - float evapSysVapPressure(); - uint8_t absBaroPressure(); - float catTempB1S1(); - float catTempB2S1(); - float catTempB1S2(); - float catTempB2S2(); - - - uint32_t supportedPIDs_41_60(); - - uint32_t monitorDriveCycleStatus(); - float ctrlModVoltage(); - float absLoad(); - float commandedAirFuelRatio(); - float relativeThrottle(); - float ambientAirTemp(); - float absThrottlePosB(); - float absThrottlePosC(); - float absThrottlePosD(); - float absThrottlePosE(); - float absThrottlePosF(); - float commandedThrottleActuator(); - uint16_t timeRunWithMIL(); - uint16_t timeSinceCodesCleared(); - float maxMafRate(); - uint8_t fuelType(); - float ethonolPercent(); - float absEvapSysVapPressure(); - float evapSysVapPressure2(); - float absFuelRailPressure(); - float relativePedalPos(); - float hybridBatLife(); - float oilTemp(); - float fuelInjectTiming(); - float fuelRate(); - uint8_t emissionRqmts(); - - - uint32_t supportedPIDs_61_80(); - - float demandedTorque(); - float torque(); - uint16_t referenceTorque(); - uint16_t auxSupported(); - void printError(); - + Stream* elm_port; + + bool connected = false; + bool specifyNumResponses = true; + bool debugMode; + char* payload; + uint16_t PAYLOAD_LEN; + int8_t nb_rx_state = ELM_GETTING_MSG; + uint64_t response = 0; + uint16_t recBytes; + uint8_t numPayChars; + uint16_t timeout_ms; + byte responseByte_0; + byte responseByte_1; + byte responseByte_2; + byte responseByte_3; + byte responseByte_4; + byte responseByte_5; + byte responseByte_6; + byte responseByte_7; + + + struct dtcResponse { + uint8_t codesFound = 0; + char codes[DTC_MAX_CODES][DTC_CODE_LEN]; + } DTC_Response; + + bool begin(Stream& stream, const bool& debug = false, const uint16_t& timeout = 1000, const char& protocol = '0', const uint16_t& payloadLen = 128, const byte& dataTimeout = 0); + ~ELM327(); + bool initializeELM(const char& protocol = '0', const byte& dataTimeout = 0); + void flushInputBuff(); + uint64_t findResponse(); + void queryPID(const uint8_t& service, const uint16_t& pid, const uint8_t& num_responses = 1); + void queryPID(char queryStr[]); + double processPID(const uint8_t& service, const uint16_t& pid, const uint8_t& num_responses, const uint8_t& numExpectedBytes, const double& scaleFactor = 1, const float& bias = 0); + void sendCommand(const char *cmd); + int8_t sendCommand_Blocking(const char *cmd); + int8_t get_response(); + bool timeout(); + double conditionResponse(const uint8_t& numExpectedBytes, const double& scaleFactor = 1, const double& bias = 0); + double conditionResponse(double (*func)()); + double (*selectCalculator(uint16_t pid))(); + float batteryVoltage(void); + int8_t get_vin_blocking(char vin[]); + bool resetDTC(); + void currentDTCCodes(const bool& isBlocking = true); + bool isPidSupported(uint8_t pid); + void parseMultiLineResponse(); + + uint32_t supportedPIDs_1_20(); + + uint32_t monitorStatus(); + uint16_t freezeDTC(); + uint16_t fuelSystemStatus(); + float engineLoad(); + float engineCoolantTemp(); + float shortTermFuelTrimBank_1(); + float longTermFuelTrimBank_1(); + float shortTermFuelTrimBank_2(); + float longTermFuelTrimBank_2(); + float fuelPressure(); + uint8_t manifoldPressure(); + float rpm(); + int32_t kph(); + float mph(); + float timingAdvance(); + float intakeAirTemp(); + float mafRate(); + float throttle(); + uint8_t commandedSecAirStatus(); + uint8_t oxygenSensorsPresent_2banks(); + uint8_t obdStandards(); + uint8_t oxygenSensorsPresent_4banks(); + bool auxInputStatus(); + uint16_t runTime(); + + + uint32_t supportedPIDs_21_40(); + + uint16_t distTravelWithMIL(); + float fuelRailPressure(); + float fuelRailGuagePressure(); + float commandedEGR(); + float egrError(); + float commandedEvapPurge(); + float fuelLevel(); + uint8_t warmUpsSinceCodesCleared(); + uint16_t distSinceCodesCleared(); + float evapSysVapPressure(); + uint8_t absBaroPressure(); + float catTempB1S1(); + float catTempB2S1(); + float catTempB1S2(); + float catTempB2S2(); + + uint32_t supportedPIDs_41_60(); + + uint32_t monitorDriveCycleStatus(); + float ctrlModVoltage(); + float absLoad(); + float commandedAirFuelRatio(); + float relativeThrottle(); + float ambientAirTemp(); + float absThrottlePosB(); + float absThrottlePosC(); + float absThrottlePosD(); + float absThrottlePosE(); + float absThrottlePosF(); + float commandedThrottleActuator(); + uint16_t timeRunWithMIL(); + uint16_t timeSinceCodesCleared(); + float maxMafRate(); + uint8_t fuelType(); + float ethanolPercent(); + float absEvapSysVapPressure(); + float evapSysVapPressure2(); + float absFuelRailPressure(); + float relativePedalPos(); + float hybridBatLife(); + float oilTemp(); + float fuelInjectTiming(); + float fuelRate(); + uint8_t emissionRqmts(); + + + uint32_t supportedPIDs_61_80(); + + float demandedTorque(); + float torque(); + uint16_t referenceTorque(); + uint16_t auxSupported(); + void printError(); -private: - char query[QUERY_LEN] = { '\0' }; - bool longQuery = false; - uint32_t currentTime; - uint32_t previousTime; - - - - void upper(char string[], uint8_t buflen); - void formatQueryArray(uint8_t service, uint16_t pid); - uint8_t ctoi(uint8_t value); - int8_t nextIndex(char const *str, - char const *target, - uint8_t numOccur); - float conditionResponse(const uint64_t& response, const uint8_t& numExpectedBytes, const float& scaleFactor = 1, const float& bias = 0); +private: + char query[QUERY_LEN] = { '\0' }; + bool longQuery = false; + bool isMode0x22Query = false; + uint32_t currentTime; + uint32_t previousTime; + double* calculator; + + static double calculator_0C(); + static double calculator_10(); + static double calculator_14(); + static double calculator_1F(); + static double calculator_22(); + static double calculator_23(); + static double calculator_24(); + static double calculator_32(); + static double calculator_3C(); + static double calculator_42(); + static double calculator_43(); + static double calculator_44(); + static double calculator_4F(); + static double calculator_50(); + static double calculator_53(); + static double calculator_54(); + static double calculator_55(); + static double calculator_59(); + static double calculator_5D(); + static double calculator_5E(); + static double calculator_61(); + + obd_cmd_states nb_query_state = SEND_COMMAND; // Non-blocking query state + + void upper(char string[], + uint8_t buflen); + void formatQueryArray(const uint8_t& service, + const uint16_t& pid, + const uint8_t& num_responses); + + uint8_t ctoi(uint8_t value); + int8_t nextIndex(char const *str, + char const *target, + uint8_t numOccur = 1); + void removeChar(char *from, const char *remove); };