From 350e826b28d41e1c8eb0c4ac2540a213478e08c2 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Sun, 24 Apr 2016 13:22:45 -0400 Subject: [PATCH 001/128] using byte literals for commands --- obd/commands.py | 215 +++++++++++++++++++++++++----------------------- obd/elm327.py | 28 +++---- obd/obd.py | 4 +- 3 files changed, 128 insertions(+), 119 deletions(-) diff --git a/obd/commands.py b/obd/commands.py index e166d4a5..1789f3dd 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -48,106 +48,106 @@ __mode1__ = [ # name description cmd bytes decoder ECU fast - OBDCommand("PIDS_A" , "Supported PIDs [01-20]" , "0100", 4, pid, ECU.ENGINE, True), - OBDCommand("STATUS" , "Status since DTCs cleared" , "0101", 4, status, ECU.ENGINE, True), - OBDCommand("FREEZE_DTC" , "Freeze DTC" , "0102", 2, drop, ECU.ENGINE, True), - OBDCommand("FUEL_STATUS" , "Fuel System Status" , "0103", 2, fuel_status, ECU.ENGINE, True), - OBDCommand("ENGINE_LOAD" , "Calculated Engine Load" , "0104", 1, percent, ECU.ENGINE, True), - OBDCommand("COOLANT_TEMP" , "Engine Coolant Temperature" , "0105", 1, temp, ECU.ENGINE, True), - OBDCommand("SHORT_FUEL_TRIM_1" , "Short Term Fuel Trim - Bank 1" , "0106", 1, percent_centered, ECU.ENGINE, True), - OBDCommand("LONG_FUEL_TRIM_1" , "Long Term Fuel Trim - Bank 1" , "0107", 1, percent_centered, ECU.ENGINE, True), - OBDCommand("SHORT_FUEL_TRIM_2" , "Short Term Fuel Trim - Bank 2" , "0108", 1, percent_centered, ECU.ENGINE, True), - OBDCommand("LONG_FUEL_TRIM_2" , "Long Term Fuel Trim - Bank 2" , "0109", 1, percent_centered, ECU.ENGINE, True), - OBDCommand("FUEL_PRESSURE" , "Fuel Pressure" , "010A", 1, fuel_pressure, ECU.ENGINE, True), - OBDCommand("INTAKE_PRESSURE" , "Intake Manifold Pressure" , "010B", 1, pressure, ECU.ENGINE, True), - OBDCommand("RPM" , "Engine RPM" , "010C", 2, rpm, ECU.ENGINE, True), - OBDCommand("SPEED" , "Vehicle Speed" , "010D", 1, speed, ECU.ENGINE, True), - OBDCommand("TIMING_ADVANCE" , "Timing Advance" , "010E", 1, timing_advance, ECU.ENGINE, True), - OBDCommand("INTAKE_TEMP" , "Intake Air Temp" , "010F", 1, temp, ECU.ENGINE, True), - OBDCommand("MAF" , "Air Flow Rate (MAF)" , "0110", 2, maf, ECU.ENGINE, True), - OBDCommand("THROTTLE_POS" , "Throttle Position" , "0111", 1, percent, ECU.ENGINE, True), - OBDCommand("AIR_STATUS" , "Secondary Air Status" , "0112", 1, air_status, ECU.ENGINE, True), - OBDCommand("O2_SENSORS" , "O2 Sensors Present" , "0113", 1, drop, ECU.ENGINE, True), - OBDCommand("O2_B1S1" , "O2: Bank 1 - Sensor 1 Voltage" , "0114", 2, sensor_voltage, ECU.ENGINE, True), - OBDCommand("O2_B1S2" , "O2: Bank 1 - Sensor 2 Voltage" , "0115", 2, sensor_voltage, ECU.ENGINE, True), - OBDCommand("O2_B1S3" , "O2: Bank 1 - Sensor 3 Voltage" , "0116", 2, sensor_voltage, ECU.ENGINE, True), - OBDCommand("O2_B1S4" , "O2: Bank 1 - Sensor 4 Voltage" , "0117", 2, sensor_voltage, ECU.ENGINE, True), - OBDCommand("O2_B2S1" , "O2: Bank 2 - Sensor 1 Voltage" , "0118", 2, sensor_voltage, ECU.ENGINE, True), - OBDCommand("O2_B2S2" , "O2: Bank 2 - Sensor 2 Voltage" , "0119", 2, sensor_voltage, ECU.ENGINE, True), - OBDCommand("O2_B2S3" , "O2: Bank 2 - Sensor 3 Voltage" , "011A", 2, sensor_voltage, ECU.ENGINE, True), - OBDCommand("O2_B2S4" , "O2: Bank 2 - Sensor 4 Voltage" , "011B", 2, sensor_voltage, ECU.ENGINE, True), - OBDCommand("OBD_COMPLIANCE" , "OBD Standards Compliance" , "011C", 1, obd_compliance, ECU.ENGINE, True), - OBDCommand("O2_SENSORS_ALT" , "O2 Sensors Present (alternate)" , "011D", 1, drop, ECU.ENGINE, True), - OBDCommand("AUX_INPUT_STATUS" , "Auxiliary input status" , "011E", 1, drop, ECU.ENGINE, True), - OBDCommand("RUN_TIME" , "Engine Run Time" , "011F", 2, seconds, ECU.ENGINE, True), + OBDCommand("PIDS_A" , "Supported PIDs [01-20]" , b"0100", 4, pid, ECU.ENGINE, True), + OBDCommand("STATUS" , "Status since DTCs cleared" , b"0101", 4, status, ECU.ENGINE, True), + OBDCommand("FREEZE_DTC" , "Freeze DTC" , b"0102", 2, drop, ECU.ENGINE, True), + OBDCommand("FUEL_STATUS" , "Fuel System Status" , b"0103", 2, fuel_status, ECU.ENGINE, True), + OBDCommand("ENGINE_LOAD" , "Calculated Engine Load" , b"0104", 1, percent, ECU.ENGINE, True), + OBDCommand("COOLANT_TEMP" , "Engine Coolant Temperature" , b"0105", 1, temp, ECU.ENGINE, True), + OBDCommand("SHORT_FUEL_TRIM_1" , "Short Term Fuel Trim - Bank 1" , b"0106", 1, percent_centered, ECU.ENGINE, True), + OBDCommand("LONG_FUEL_TRIM_1" , "Long Term Fuel Trim - Bank 1" , b"0107", 1, percent_centered, ECU.ENGINE, True), + OBDCommand("SHORT_FUEL_TRIM_2" , "Short Term Fuel Trim - Bank 2" , b"0108", 1, percent_centered, ECU.ENGINE, True), + OBDCommand("LONG_FUEL_TRIM_2" , "Long Term Fuel Trim - Bank 2" , b"0109", 1, percent_centered, ECU.ENGINE, True), + OBDCommand("FUEL_PRESSURE" , "Fuel Pressure" , b"010A", 1, fuel_pressure, ECU.ENGINE, True), + OBDCommand("INTAKE_PRESSURE" , "Intake Manifold Pressure" , b"010B", 1, pressure, ECU.ENGINE, True), + OBDCommand("RPM" , "Engine RPM" , b"010C", 2, rpm, ECU.ENGINE, True), + OBDCommand("SPEED" , "Vehicle Speed" , b"010D", 1, speed, ECU.ENGINE, True), + OBDCommand("TIMING_ADVANCE" , "Timing Advance" , b"010E", 1, timing_advance, ECU.ENGINE, True), + OBDCommand("INTAKE_TEMP" , "Intake Air Temp" , b"010F", 1, temp, ECU.ENGINE, True), + OBDCommand("MAF" , "Air Flow Rate (MAF)" , b"0110", 2, maf, ECU.ENGINE, True), + OBDCommand("THROTTLE_POS" , "Throttle Position" , b"0111", 1, percent, ECU.ENGINE, True), + OBDCommand("AIR_STATUS" , "Secondary Air Status" , b"0112", 1, air_status, ECU.ENGINE, True), + OBDCommand("O2_SENSORS" , "O2 Sensors Present" , b"0113", 1, drop, ECU.ENGINE, True), + OBDCommand("O2_B1S1" , "O2: Bank 1 - Sensor 1 Voltage" , b"0114", 2, sensor_voltage, ECU.ENGINE, True), + OBDCommand("O2_B1S2" , "O2: Bank 1 - Sensor 2 Voltage" , b"0115", 2, sensor_voltage, ECU.ENGINE, True), + OBDCommand("O2_B1S3" , "O2: Bank 1 - Sensor 3 Voltage" , b"0116", 2, sensor_voltage, ECU.ENGINE, True), + OBDCommand("O2_B1S4" , "O2: Bank 1 - Sensor 4 Voltage" , b"0117", 2, sensor_voltage, ECU.ENGINE, True), + OBDCommand("O2_B2S1" , "O2: Bank 2 - Sensor 1 Voltage" , b"0118", 2, sensor_voltage, ECU.ENGINE, True), + OBDCommand("O2_B2S2" , "O2: Bank 2 - Sensor 2 Voltage" , b"0119", 2, sensor_voltage, ECU.ENGINE, True), + OBDCommand("O2_B2S3" , "O2: Bank 2 - Sensor 3 Voltage" , b"011A", 2, sensor_voltage, ECU.ENGINE, True), + OBDCommand("O2_B2S4" , "O2: Bank 2 - Sensor 4 Voltage" , b"011B", 2, sensor_voltage, ECU.ENGINE, True), + OBDCommand("OBD_COMPLIANCE" , "OBD Standards Compliance" , b"011C", 1, obd_compliance, ECU.ENGINE, True), + OBDCommand("O2_SENSORS_ALT" , "O2 Sensors Present (alternate)" , b"011D", 1, drop, ECU.ENGINE, True), + OBDCommand("AUX_INPUT_STATUS" , "Auxiliary input status" , b"011E", 1, drop, ECU.ENGINE, True), + OBDCommand("RUN_TIME" , "Engine Run Time" , b"011F", 2, seconds, ECU.ENGINE, True), # name description cmd bytes decoder ECU fast - OBDCommand("PIDS_B" , "Supported PIDs [21-40]" , "0120", 4, pid, ECU.ENGINE, True), - OBDCommand("DISTANCE_W_MIL" , "Distance Traveled with MIL on" , "0121", 2, distance, ECU.ENGINE, True), - OBDCommand("FUEL_RAIL_PRESSURE_VAC" , "Fuel Rail Pressure (relative to vacuum)" , "0122", 2, fuel_pres_vac, ECU.ENGINE, True), - OBDCommand("FUEL_RAIL_PRESSURE_DIRECT" , "Fuel Rail Pressure (direct inject)" , "0123", 2, fuel_pres_direct, ECU.ENGINE, True), - OBDCommand("O2_S1_WR_VOLTAGE" , "02 Sensor 1 WR Lambda Voltage" , "0124", 4, sensor_voltage_big, ECU.ENGINE, True), - OBDCommand("O2_S2_WR_VOLTAGE" , "02 Sensor 2 WR Lambda Voltage" , "0125", 4, sensor_voltage_big, ECU.ENGINE, True), - OBDCommand("O2_S3_WR_VOLTAGE" , "02 Sensor 3 WR Lambda Voltage" , "0126", 4, sensor_voltage_big, ECU.ENGINE, True), - OBDCommand("O2_S4_WR_VOLTAGE" , "02 Sensor 4 WR Lambda Voltage" , "0127", 4, sensor_voltage_big, ECU.ENGINE, True), - OBDCommand("O2_S5_WR_VOLTAGE" , "02 Sensor 5 WR Lambda Voltage" , "0128", 4, sensor_voltage_big, ECU.ENGINE, True), - OBDCommand("O2_S6_WR_VOLTAGE" , "02 Sensor 6 WR Lambda Voltage" , "0129", 4, sensor_voltage_big, ECU.ENGINE, True), - OBDCommand("O2_S7_WR_VOLTAGE" , "02 Sensor 7 WR Lambda Voltage" , "012A", 4, sensor_voltage_big, ECU.ENGINE, True), - OBDCommand("O2_S8_WR_VOLTAGE" , "02 Sensor 8 WR Lambda Voltage" , "012B", 4, sensor_voltage_big, ECU.ENGINE, True), - OBDCommand("COMMANDED_EGR" , "Commanded EGR" , "012C", 1, percent, ECU.ENGINE, True), - OBDCommand("EGR_ERROR" , "EGR Error" , "012D", 1, percent_centered, ECU.ENGINE, True), - OBDCommand("EVAPORATIVE_PURGE" , "Commanded Evaporative Purge" , "012E", 1, percent, ECU.ENGINE, True), - OBDCommand("FUEL_LEVEL" , "Fuel Level Input" , "012F", 1, percent, ECU.ENGINE, True), - OBDCommand("WARMUPS_SINCE_DTC_CLEAR" , "Number of warm-ups since codes cleared" , "0130", 1, count, ECU.ENGINE, True), - OBDCommand("DISTANCE_SINCE_DTC_CLEAR" , "Distance traveled since codes cleared" , "0131", 2, distance, ECU.ENGINE, True), - OBDCommand("EVAP_VAPOR_PRESSURE" , "Evaporative system vapor pressure" , "0132", 2, evap_pressure, ECU.ENGINE, True), - OBDCommand("BAROMETRIC_PRESSURE" , "Barometric Pressure" , "0133", 1, pressure, ECU.ENGINE, True), - OBDCommand("O2_S1_WR_CURRENT" , "02 Sensor 1 WR Lambda Current" , "0134", 4, current_centered, ECU.ENGINE, True), - OBDCommand("O2_S2_WR_CURRENT" , "02 Sensor 2 WR Lambda Current" , "0135", 4, current_centered, ECU.ENGINE, True), - OBDCommand("O2_S3_WR_CURRENT" , "02 Sensor 3 WR Lambda Current" , "0136", 4, current_centered, ECU.ENGINE, True), - OBDCommand("O2_S4_WR_CURRENT" , "02 Sensor 4 WR Lambda Current" , "0137", 4, current_centered, ECU.ENGINE, True), - OBDCommand("O2_S5_WR_CURRENT" , "02 Sensor 5 WR Lambda Current" , "0138", 4, current_centered, ECU.ENGINE, True), - OBDCommand("O2_S6_WR_CURRENT" , "02 Sensor 6 WR Lambda Current" , "0139", 4, current_centered, ECU.ENGINE, True), - OBDCommand("O2_S7_WR_CURRENT" , "02 Sensor 7 WR Lambda Current" , "013A", 4, current_centered, ECU.ENGINE, True), - OBDCommand("O2_S8_WR_CURRENT" , "02 Sensor 8 WR Lambda Current" , "013B", 4, current_centered, ECU.ENGINE, True), - OBDCommand("CATALYST_TEMP_B1S1" , "Catalyst Temperature: Bank 1 - Sensor 1" , "013C", 2, catalyst_temp, ECU.ENGINE, True), - OBDCommand("CATALYST_TEMP_B2S1" , "Catalyst Temperature: Bank 2 - Sensor 1" , "013D", 2, catalyst_temp, ECU.ENGINE, True), - OBDCommand("CATALYST_TEMP_B1S2" , "Catalyst Temperature: Bank 1 - Sensor 2" , "013E", 2, catalyst_temp, ECU.ENGINE, True), - OBDCommand("CATALYST_TEMP_B2S2" , "Catalyst Temperature: Bank 2 - Sensor 2" , "013F", 2, catalyst_temp, ECU.ENGINE, True), + OBDCommand("PIDS_B" , "Supported PIDs [21-40]" , b"0120", 4, pid, ECU.ENGINE, True), + OBDCommand("DISTANCE_W_MIL" , "Distance Traveled with MIL on" , b"0121", 2, distance, ECU.ENGINE, True), + OBDCommand("FUEL_RAIL_PRESSURE_VAC" , "Fuel Rail Pressure (relative to vacuum)" , b"0122", 2, fuel_pres_vac, ECU.ENGINE, True), + OBDCommand("FUEL_RAIL_PRESSURE_DIRECT" , "Fuel Rail Pressure (direct inject)" , b"0123", 2, fuel_pres_direct, ECU.ENGINE, True), + OBDCommand("O2_S1_WR_VOLTAGE" , "02 Sensor 1 WR Lambda Voltage" , b"0124", 4, sensor_voltage_big, ECU.ENGINE, True), + OBDCommand("O2_S2_WR_VOLTAGE" , "02 Sensor 2 WR Lambda Voltage" , b"0125", 4, sensor_voltage_big, ECU.ENGINE, True), + OBDCommand("O2_S3_WR_VOLTAGE" , "02 Sensor 3 WR Lambda Voltage" , b"0126", 4, sensor_voltage_big, ECU.ENGINE, True), + OBDCommand("O2_S4_WR_VOLTAGE" , "02 Sensor 4 WR Lambda Voltage" , b"0127", 4, sensor_voltage_big, ECU.ENGINE, True), + OBDCommand("O2_S5_WR_VOLTAGE" , "02 Sensor 5 WR Lambda Voltage" , b"0128", 4, sensor_voltage_big, ECU.ENGINE, True), + OBDCommand("O2_S6_WR_VOLTAGE" , "02 Sensor 6 WR Lambda Voltage" , b"0129", 4, sensor_voltage_big, ECU.ENGINE, True), + OBDCommand("O2_S7_WR_VOLTAGE" , "02 Sensor 7 WR Lambda Voltage" , b"012A", 4, sensor_voltage_big, ECU.ENGINE, True), + OBDCommand("O2_S8_WR_VOLTAGE" , "02 Sensor 8 WR Lambda Voltage" , b"012B", 4, sensor_voltage_big, ECU.ENGINE, True), + OBDCommand("COMMANDED_EGR" , "Commanded EGR" , b"012C", 1, percent, ECU.ENGINE, True), + OBDCommand("EGR_ERROR" , "EGR Error" , b"012D", 1, percent_centered, ECU.ENGINE, True), + OBDCommand("EVAPORATIVE_PURGE" , "Commanded Evaporative Purge" , b"012E", 1, percent, ECU.ENGINE, True), + OBDCommand("FUEL_LEVEL" , "Fuel Level Input" , b"012F", 1, percent, ECU.ENGINE, True), + OBDCommand("WARMUPS_SINCE_DTC_CLEAR" , "Number of warm-ups since codes cleared" , b"0130", 1, count, ECU.ENGINE, True), + OBDCommand("DISTANCE_SINCE_DTC_CLEAR" , "Distance traveled since codes cleared" , b"0131", 2, distance, ECU.ENGINE, True), + OBDCommand("EVAP_VAPOR_PRESSURE" , "Evaporative system vapor pressure" , b"0132", 2, evap_pressure, ECU.ENGINE, True), + OBDCommand("BAROMETRIC_PRESSURE" , "Barometric Pressure" , b"0133", 1, pressure, ECU.ENGINE, True), + OBDCommand("O2_S1_WR_CURRENT" , "02 Sensor 1 WR Lambda Current" , b"0134", 4, current_centered, ECU.ENGINE, True), + OBDCommand("O2_S2_WR_CURRENT" , "02 Sensor 2 WR Lambda Current" , b"0135", 4, current_centered, ECU.ENGINE, True), + OBDCommand("O2_S3_WR_CURRENT" , "02 Sensor 3 WR Lambda Current" , b"0136", 4, current_centered, ECU.ENGINE, True), + OBDCommand("O2_S4_WR_CURRENT" , "02 Sensor 4 WR Lambda Current" , b"0137", 4, current_centered, ECU.ENGINE, True), + OBDCommand("O2_S5_WR_CURRENT" , "02 Sensor 5 WR Lambda Current" , b"0138", 4, current_centered, ECU.ENGINE, True), + OBDCommand("O2_S6_WR_CURRENT" , "02 Sensor 6 WR Lambda Current" , b"0139", 4, current_centered, ECU.ENGINE, True), + OBDCommand("O2_S7_WR_CURRENT" , "02 Sensor 7 WR Lambda Current" , b"013A", 4, current_centered, ECU.ENGINE, True), + OBDCommand("O2_S8_WR_CURRENT" , "02 Sensor 8 WR Lambda Current" , b"013B", 4, current_centered, ECU.ENGINE, True), + OBDCommand("CATALYST_TEMP_B1S1" , "Catalyst Temperature: Bank 1 - Sensor 1" , b"013C", 2, catalyst_temp, ECU.ENGINE, True), + OBDCommand("CATALYST_TEMP_B2S1" , "Catalyst Temperature: Bank 2 - Sensor 1" , b"013D", 2, catalyst_temp, ECU.ENGINE, True), + OBDCommand("CATALYST_TEMP_B1S2" , "Catalyst Temperature: Bank 1 - Sensor 2" , b"013E", 2, catalyst_temp, ECU.ENGINE, True), + OBDCommand("CATALYST_TEMP_B2S2" , "Catalyst Temperature: Bank 2 - Sensor 2" , b"013F", 2, catalyst_temp, ECU.ENGINE, True), # name description cmd bytes decoder ECU fast - OBDCommand("PIDS_C" , "Supported PIDs [41-60]" , "0140", 4, pid, ECU.ENGINE, True), - OBDCommand("STATUS_DRIVE_CYCLE" , "Monitor status this drive cycle" , "0141", 4, drop, ECU.ENGINE, True), - OBDCommand("CONTROL_MODULE_VOLTAGE" , "Control module voltage" , "0142", 2, drop, ECU.ENGINE, True), - OBDCommand("ABSOLUTE_LOAD" , "Absolute load value" , "0143", 2, drop, ECU.ENGINE, True), - OBDCommand("COMMAND_EQUIV_RATIO" , "Command equivalence ratio" , "0144", 2, drop, ECU.ENGINE, True), - OBDCommand("RELATIVE_THROTTLE_POS" , "Relative throttle position" , "0145", 1, percent, ECU.ENGINE, True), - OBDCommand("AMBIANT_AIR_TEMP" , "Ambient air temperature" , "0146", 1, temp, ECU.ENGINE, True), - OBDCommand("THROTTLE_POS_B" , "Absolute throttle position B" , "0147", 1, percent, ECU.ENGINE, True), - OBDCommand("THROTTLE_POS_C" , "Absolute throttle position C" , "0148", 1, percent, ECU.ENGINE, True), - OBDCommand("ACCELERATOR_POS_D" , "Accelerator pedal position D" , "0149", 1, percent, ECU.ENGINE, True), - OBDCommand("ACCELERATOR_POS_E" , "Accelerator pedal position E" , "014A", 1, percent, ECU.ENGINE, True), - OBDCommand("ACCELERATOR_POS_F" , "Accelerator pedal position F" , "014B", 1, percent, ECU.ENGINE, True), - OBDCommand("THROTTLE_ACTUATOR" , "Commanded throttle actuator" , "014C", 1, percent, ECU.ENGINE, True), - OBDCommand("RUN_TIME_MIL" , "Time run with MIL on" , "014D", 2, minutes, ECU.ENGINE, True), - OBDCommand("TIME_SINCE_DTC_CLEARED" , "Time since trouble codes cleared" , "014E", 2, minutes, ECU.ENGINE, True), - OBDCommand("MAX_VALUES" , "Various Max values" , "014F", 4, drop, ECU.ENGINE, True), # todo: decode this - OBDCommand("MAX_MAF" , "Maximum value for mass air flow sensor" , "0150", 4, max_maf, ECU.ENGINE, True), - OBDCommand("FUEL_TYPE" , "Fuel Type" , "0151", 1, fuel_type, ECU.ENGINE, True), - OBDCommand("ETHANOL_PERCENT" , "Ethanol Fuel Percent" , "0152", 1, percent, ECU.ENGINE, True), - OBDCommand("EVAP_VAPOR_PRESSURE_ABS" , "Absolute Evap system Vapor Pressure" , "0153", 2, abs_evap_pressure, ECU.ENGINE, True), - OBDCommand("EVAP_VAPOR_PRESSURE_ALT" , "Evap system vapor pressure" , "0154", 2, evap_pressure_alt, ECU.ENGINE, True), - OBDCommand("SHORT_O2_TRIM_B1" , "Short term secondary O2 trim - Bank 1" , "0155", 2, percent_centered, ECU.ENGINE, True), # todo: decode seconds value for banks 3 and 4 - OBDCommand("LONG_O2_TRIM_B1" , "Long term secondary O2 trim - Bank 1" , "0156", 2, percent_centered, ECU.ENGINE, True), - OBDCommand("SHORT_O2_TRIM_B2" , "Short term secondary O2 trim - Bank 2" , "0157", 2, percent_centered, ECU.ENGINE, True), - OBDCommand("LONG_O2_TRIM_B2" , "Long term secondary O2 trim - Bank 2" , "0158", 2, percent_centered, ECU.ENGINE, True), - OBDCommand("FUEL_RAIL_PRESSURE_ABS" , "Fuel rail pressure (absolute)" , "0159", 2, fuel_pres_direct, ECU.ENGINE, True), - OBDCommand("RELATIVE_ACCEL_POS" , "Relative accelerator pedal position" , "015A", 1, percent, ECU.ENGINE, True), - OBDCommand("HYBRID_BATTERY_REMAINING" , "Hybrid battery pack remaining life" , "015B", 1, percent, ECU.ENGINE, True), - OBDCommand("OIL_TEMP" , "Engine oil temperature" , "015C", 1, temp, ECU.ENGINE, True), - OBDCommand("FUEL_INJECT_TIMING" , "Fuel injection timing" , "015D", 2, inject_timing, ECU.ENGINE, True), - OBDCommand("FUEL_RATE" , "Engine fuel rate" , "015E", 2, fuel_rate, ECU.ENGINE, True), - OBDCommand("EMISSION_REQ" , "Designed emission requirements" , "015F", 1, drop, ECU.ENGINE, True), + OBDCommand("PIDS_C" , "Supported PIDs [41-60]" , b"0140", 4, pid, ECU.ENGINE, True), + OBDCommand("STATUS_DRIVE_CYCLE" , "Monitor status this drive cycle" , b"0141", 4, drop, ECU.ENGINE, True), + OBDCommand("CONTROL_MODULE_VOLTAGE" , "Control module voltage" , b"0142", 2, drop, ECU.ENGINE, True), + OBDCommand("ABSOLUTE_LOAD" , "Absolute load value" , b"0143", 2, drop, ECU.ENGINE, True), + OBDCommand("COMMAND_EQUIV_RATIO" , "Command equivalence ratio" , b"0144", 2, drop, ECU.ENGINE, True), + OBDCommand("RELATIVE_THROTTLE_POS" , "Relative throttle position" , b"0145", 1, percent, ECU.ENGINE, True), + OBDCommand("AMBIANT_AIR_TEMP" , "Ambient air temperature" , b"0146", 1, temp, ECU.ENGINE, True), + OBDCommand("THROTTLE_POS_B" , "Absolute throttle position B" , b"0147", 1, percent, ECU.ENGINE, True), + OBDCommand("THROTTLE_POS_C" , "Absolute throttle position C" , b"0148", 1, percent, ECU.ENGINE, True), + OBDCommand("ACCELERATOR_POS_D" , "Accelerator pedal position D" , b"0149", 1, percent, ECU.ENGINE, True), + OBDCommand("ACCELERATOR_POS_E" , "Accelerator pedal position E" , b"014A", 1, percent, ECU.ENGINE, True), + OBDCommand("ACCELERATOR_POS_F" , "Accelerator pedal position F" , b"014B", 1, percent, ECU.ENGINE, True), + OBDCommand("THROTTLE_ACTUATOR" , "Commanded throttle actuator" , b"014C", 1, percent, ECU.ENGINE, True), + OBDCommand("RUN_TIME_MIL" , "Time run with MIL on" , b"014D", 2, minutes, ECU.ENGINE, True), + OBDCommand("TIME_SINCE_DTC_CLEARED" , "Time since trouble codes cleared" , b"014E", 2, minutes, ECU.ENGINE, True), + OBDCommand("MAX_VALUES" , "Various Max values" , b"014F", 4, drop, ECU.ENGINE, True), # todo: decode this + OBDCommand("MAX_MAF" , "Maximum value for mass air flow sensor" , b"0150", 4, max_maf, ECU.ENGINE, True), + OBDCommand("FUEL_TYPE" , "Fuel Type" , b"0151", 1, fuel_type, ECU.ENGINE, True), + OBDCommand("ETHANOL_PERCENT" , "Ethanol Fuel Percent" , b"0152", 1, percent, ECU.ENGINE, True), + OBDCommand("EVAP_VAPOR_PRESSURE_ABS" , "Absolute Evap system Vapor Pressure" , b"0153", 2, abs_evap_pressure, ECU.ENGINE, True), + OBDCommand("EVAP_VAPOR_PRESSURE_ALT" , "Evap system vapor pressure" , b"0154", 2, evap_pressure_alt, ECU.ENGINE, True), + OBDCommand("SHORT_O2_TRIM_B1" , "Short term secondary O2 trim - Bank 1" , b"0155", 2, percent_centered, ECU.ENGINE, True), # todo: decode seconds value for banks 3 and 4 + OBDCommand("LONG_O2_TRIM_B1" , "Long term secondary O2 trim - Bank 1" , b"0156", 2, percent_centered, ECU.ENGINE, True), + OBDCommand("SHORT_O2_TRIM_B2" , "Short term secondary O2 trim - Bank 2" , b"0157", 2, percent_centered, ECU.ENGINE, True), + OBDCommand("LONG_O2_TRIM_B2" , "Long term secondary O2 trim - Bank 2" , b"0158", 2, percent_centered, ECU.ENGINE, True), + OBDCommand("FUEL_RAIL_PRESSURE_ABS" , "Fuel rail pressure (absolute)" , b"0159", 2, fuel_pres_direct, ECU.ENGINE, True), + OBDCommand("RELATIVE_ACCEL_POS" , "Relative accelerator pedal position" , b"015A", 1, percent, ECU.ENGINE, True), + OBDCommand("HYBRID_BATTERY_REMAINING" , "Hybrid battery pack remaining life" , b"015B", 1, percent, ECU.ENGINE, True), + OBDCommand("OIL_TEMP" , "Engine oil temperature" , b"015C", 1, temp, ECU.ENGINE, True), + OBDCommand("FUEL_INJECT_TIMING" , "Fuel injection timing" , b"015D", 2, inject_timing, ECU.ENGINE, True), + OBDCommand("FUEL_RATE" , "Engine fuel rate" , b"015E", 2, fuel_rate, ECU.ENGINE, True), + OBDCommand("EMISSION_REQ" , "Designed emission requirements" , b"015F", 1, drop, ECU.ENGINE, True), ] @@ -155,7 +155,7 @@ __mode2__ = [] for c in __mode1__: c = c.clone() - c.command = "02" + c.command[2:] # change the mode: 0100 ---> 0200 + c.command = b"02" + c.command[2:] # change the mode: 0100 ---> 0200 c.name = "DTC_" + c.name c.desc = "DTC " + c.desc __mode2__.append(c) @@ -163,23 +163,30 @@ __mode3__ = [ # name description cmd bytes decoder ECU fast - OBDCommand("GET_DTC" , "Get DTCs" , "03", 0, dtc, ECU.ALL, False), + OBDCommand("GET_DTC" , "Get DTCs" , b"03", 0, dtc, ECU.ALL, False), ] __mode4__ = [ # name description cmd bytes decoder ECU fast - OBDCommand("CLEAR_DTC" , "Clear DTCs and Freeze data" , "04", 0, drop, ECU.ALL, False), + OBDCommand("CLEAR_DTC" , "Clear DTCs and Freeze data" , b"04", 0, drop, ECU.ALL, False), ] __mode7__ = [ # name description cmd bytes decoder ECU fast - OBDCommand("GET_FREEZE_DTC" , "Get Freeze DTCs" , "07", 0, dtc, ECU.ALL, False), + OBDCommand("GET_FREEZE_DTC" , "Get Freeze DTCs" , b"07", 0, dtc, ECU.ALL, False), +] + +__mode9__ = [ + # name description cmd bytes decoder ECU fast + OBDCommand("PIDS_9A" , "Supported PIDs [01-20]" , b"0900", 4, pid, ECU.ENGINE, True), + OBDCommand("VIN_MESSAGE_COUNT" , "VIN Message Count" , b"0901", 1, count, ECU.ENGINE, True), + OBDCommand("VIN" , "Get Vehicle Identification Number" , b"0902", 20, raw_string, ECU.ENGINE, True), ] __misc__ = [ # name description cmd bytes decoder ECU fast - OBDCommand("ELM_VERSION" , "ELM327 version string" , "ATI", 0, raw_string, ECU.UNKNOWN, False), - OBDCommand("ELM_VOLTAGE" , "Voltage detected by OBD-II adapter" , "ATRV", 0, elm_voltage, ECU.UNKNOWN, False), + OBDCommand("ELM_VERSION" , "ELM327 version string" , b"ATI", 0, raw_string, ECU.UNKNOWN, False), + OBDCommand("ELM_VOLTAGE" , "Voltage detected by OBD-II adapter" , b"ATRV", 0, elm_voltage, ECU.UNKNOWN, False), ] @@ -200,7 +207,9 @@ def __init__(self): __mode4__, [], [], - __mode7__ + __mode7__, + [], + __mode9__, ] # allow commands to be accessed by name diff --git a/obd/elm327.py b/obd/elm327.py index 0b0841f9..d965ec15 100644 --- a/obd/elm327.py +++ b/obd/elm327.py @@ -115,26 +115,26 @@ def __init__(self, portname, baudrate, protocol): # ---------------------------- ATZ (reset) ---------------------------- try: - self.__send("ATZ", delay=1) # wait 1 second for ELM to initialize + self.__send(b"ATZ", delay=1) # wait 1 second for ELM to initialize # return data can be junk, so don't bother checking except serial.SerialException as e: self.__error(e) return # -------------------------- ATE0 (echo OFF) -------------------------- - r = self.__send("ATE0") + r = self.__send(b"ATE0") if not self.__isok(r, expectEcho=True): self.__error("ATE0 did not return 'OK'") return # ------------------------- ATH1 (headers ON) ------------------------- - r = self.__send("ATH1") + r = self.__send(b"ATH1") if not self.__isok(r): self.__error("ATH1 did not return 'OK', or echoing is still ON") return # ------------------------ ATL0 (linefeeds OFF) ----------------------- - r = self.__send("ATL0") + r = self.__send(b"ATL0") if not self.__isok(r): self.__error("ATL0 did not return 'OK'") return @@ -164,8 +164,8 @@ def load_protocol(self, protocol): def manual_protocol(self, protocol): - r = self.__send("ATTP%s" % protocol) - r0100 = self.__send("0100") + r = self.__send(b"ATTP" + protocol.encode()) + r0100 = self.__send(b"0100") if not self.__has_message(r0100, "UNABLE TO CONNECT"): # success, found the protocol @@ -186,13 +186,13 @@ def auto_protocol(self): """ # -------------- try the ELM's auto protocol mode -------------- - r = self.__send("ATSP0") + r = self.__send(b"ATSP0") # -------------- 0100 (first command, SEARCH protocols) -------------- - r0100 = self.__send("0100") + r0100 = self.__send(b"0100") # ------------------- ATDPN (list protocol number) ------------------- - r = self.__send("ATDPN") + r = self.__send(b"ATDPN") if len(r) != 1: debug("Failed to retrieve current protocol", True) return False @@ -214,8 +214,8 @@ def auto_protocol(self): debug("ELM responded with unknown protocol. Trying them one-by-one") for p in self._TRY_PROTOCOL_ORDER: - r = self.__send("ATTP%s" % p) - r0100 = self.__send("0100") + r = self.__send(b"ATTP" + p.encode()) + r0100 = self.__send(b"0100") if not self.__has_message(r0100, "UNABLE TO CONNECT"): # success, found the protocol self.__protocol = self._SUPPORTED_PROTOCOLS[p](r0100) @@ -287,7 +287,7 @@ def close(self): self.__protocol = None if self.__port is not None: - self.__write("ATZ") + self.__write(b"ATZ") self.__port.close() self.__port = None @@ -337,10 +337,10 @@ def __write(self, cmd): """ if self.__port: - cmd += "\r\n" # terminate + cmd += b"\r\n" # terminate debug("write: " + repr(cmd)) self.__port.flushInput() # dump everything in the input buffer - self.__port.write(cmd.encode()) # turn the string into bytes and write + self.__port.write(cmd) # turn the string into bytes and write self.__port.flush() # wait for the output buffer to finish transmitting else: debug("cannot perform __write() when unconnected", True) diff --git a/obd/obd.py b/obd/obd.py index 14d22580..34ea764b 100644 --- a/obd/obd.py +++ b/obd/obd.py @@ -256,10 +256,10 @@ def __build_command_string(self, cmd): # only wait for as many ECUs as we've seen if self.fast and cmd.fast: - cmd_string += str(len(self.port.ecus())) + cmd_string += str(len(self.port.ecus())).encode() # if we sent this last time, just send if self.fast and (cmd_string == self.__last_command): - cmd_string = "" + cmd_string = b"" return cmd_string From d76acea8803cd8d57cd02b7d640ad74abdef7c16 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 16 Jun 2016 13:39:20 -0400 Subject: [PATCH 002/128] started implemention auto baud rate --- obd/elm327.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/obd/elm327.py b/obd/elm327.py index d965ec15..c0c482d2 100644 --- a/obd/elm327.py +++ b/obd/elm327.py @@ -85,6 +85,19 @@ class ELM327: "A", # SAE_J1939 ] + # 38400, 9600 are the possible boot bauds (unless reprogrammed via + # PP 0C). 19200, 38400, 57600, 115200, 230400, 500000 are listed on + # p.46 of the ELM327 datasheet. + # + # Once pyserial supports non-standard baud rates on platforms other + # than Linux, we'll add 500K to this list. + # + # We check the two default baud rates first, then go fastest to + # slowest, on the theory that anyone who's using a slow baud rate is + # going to be less picky about the time required to detect it. + _TRY_BAUDS = [ 38400, 9600, 230400, 115200, 57600, 19200 ] + + def __init__(self, portname, baudrate, protocol): """Initializes port by resetting device and gettings supported PIDs. """ @@ -225,6 +238,35 @@ def auto_protocol(self): return False + def auto_baudrate(self): + """Detect, select, and return the baud rate at which a connected + ELM32x interface is operating. + + Return None if the baud rate couldn't be determined. + """ + for baud in self._TRY_BAUDS: + self.port.setBaudrate(baud) + self.port.flushInput() + self.port.flushOutput() + + # Send a nonsense command to get a prompt back from the scanner + # (an empty command runs the risk of repeating a dangerous command) + # The first character might get eaten if the interface was busy, + # so write a second one (again so that the lone CR doesn't repeat + # the previous command) + port.write("\x7F\x7F\r") + port.set_timeout(timeout) + response = self.__read() + + if (response.endswith("\r\r>")): + #print "%d baud detected (%r)" % (baud, response) + break + else: + baud = None + + return baud + + def __isok(self, lines, expectEcho=False): if not lines: From 3e96a65ee534f1bd27542e644ecfd73c0eda96fe Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Tue, 28 Jun 2016 23:54:57 -0400 Subject: [PATCH 003/128] first test with python native logging tools --- obd/__init__.py | 9 +++++++++ obd/obd.py | 33 ++++++++++++++++++--------------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/obd/__init__.py b/obd/__init__.py index 51ea205c..866cbdb0 100644 --- a/obd/__init__.py +++ b/obd/__init__.py @@ -45,3 +45,12 @@ from .protocols import ECU from .utils import scan_serial, scanSerial, OBDStatus # TODO: scanSerial() deprecated from .debug import debug + +import logging + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +console_handler = logging.StreamHandler() # sends output to stderr +console_handler.setFormatter(logging.Formatter("[%(name)s] %(message)s")) +logger.addHandler(console_handler) diff --git a/obd/obd.py b/obd/obd.py index 34ea764b..3811b7b6 100644 --- a/obd/obd.py +++ b/obd/obd.py @@ -30,6 +30,8 @@ ######################################################################## +import logging + from .__version__ import __version__ from .elm327 import ELM327 from .commands import commands @@ -37,6 +39,7 @@ from .utils import scan_serial, OBDStatus from .debug import debug +logger = logging.getLogger(__name__) class OBD(object): @@ -51,10 +54,10 @@ def __init__(self, portstr=None, baudrate=38400, protocol=None, fast=True): self.fast = fast self.__last_command = "" # used for running the previous command with a CR - debug("========================== python-OBD (v%s) ==========================" % __version__) + logger.info("======================= python-OBD (v%s) =======================" % __version__) self.__connect(portstr, baudrate, protocol) # initialize by connecting and loading sensors self.__load_commands() # try to load the car's supported commands - debug("=========================================================================") + logger.info("===================================================================") def __connect(self, portstr, baudrate, protocol): @@ -63,22 +66,22 @@ def __connect(self, portstr, baudrate, protocol): """ if portstr is None: - debug("Using scan_serial to select port") + logger.info("Using scan_serial to select port") portnames = scan_serial() - debug("Available ports: " + str(portnames)) + logger.info("Available ports: " + str(portnames)) if not portnames: - debug("No OBD-II adapters found", True) + logger.warning("No OBD-II adapters found") return for port in portnames: - debug("Attempting to use port: " + str(port)) + logger.info("Attempting to use port: " + str(port)) self.port = ELM327(port, baudrate, protocol) if self.port.status() >= OBDStatus.ELM_CONNECTED: break # success! stop searching for serial else: - debug("Explicit port defined") + logger.info("Explicit port defined") self.port = ELM327(portstr, baudrate, protocol) # if the connection failed, close it @@ -94,10 +97,10 @@ def __load_commands(self): """ if self.status() != OBDStatus.CAR_CONNECTED: - debug("Cannot load commands: No connection to car", True) + logger.warning("Cannot load commands: No connection to car") return - debug("querying for supported PIDs (commands)...") + logger.info("querying for supported PIDs (commands)...") pid_getters = commands.pid_getters() for get in pid_getters: # PID listing commands should sequentialy become supported @@ -128,7 +131,7 @@ def __load_commands(self): if mode == 1 and commands.has_pid(2, pid): self.supported_commands.add(commands[2][pid]) - debug("finished querying with %d commands supported" % len(self.supported_commands)) + logger.info("finished querying with %d commands supported" % len(self.supported_commands)) def close(self): @@ -139,7 +142,7 @@ def close(self): self.supported_commands = [] if self.port is not None: - debug("Closing connection") + logger.info("Closing connection") self.port.close() self.port = None @@ -226,16 +229,16 @@ def query(self, cmd, force=False): """ if self.status() == OBDStatus.NOT_CONNECTED: - debug("Query failed, no connection available", True) + logger.warning("Query failed, no connection available") return OBDResponse() if not force and not self.supports(cmd): - debug("'%s' is not supported" % str(cmd), True) + logger.warning("'%s' is not supported" % str(cmd)) return OBDResponse() # send command and retrieve message - debug("Sending command: %s" % str(cmd)) + logger.info("Sending command: %s" % str(cmd)) cmd_string = self.__build_command_string(cmd) messages = self.port.send_and_parse(cmd_string) @@ -244,7 +247,7 @@ def query(self, cmd, force=False): self.__last_command = cmd_string if not messages: - debug("No valid OBD Messages returned", True) + logger.info("No valid OBD Messages returned") return OBDResponse() return cmd(messages) # compute a response object From 9a2241f6dfc3a31b8cd116859c0f2d3a970fddc5 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Tue, 28 Jun 2016 23:55:28 -0400 Subject: [PATCH 004/128] fixed indentation error in new baud rate code --- obd/elm327.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obd/elm327.py b/obd/elm327.py index c0c482d2..a8faa899 100644 --- a/obd/elm327.py +++ b/obd/elm327.py @@ -256,7 +256,7 @@ def auto_baudrate(self): # the previous command) port.write("\x7F\x7F\r") port.set_timeout(timeout) - response = self.__read() + response = self.__read() if (response.endswith("\r\r>")): #print "%d baud detected (%r)" % (baud, response) From bb4ade468417f885e3df3ee0f07ef50268477d37 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 29 Jun 2016 00:03:02 -0400 Subject: [PATCH 005/128] added new logging to elm327.py --- obd/__init__.py | 1 - obd/elm327.py | 39 ++++++++++++++++++++------------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/obd/__init__.py b/obd/__init__.py index 866cbdb0..582956c9 100644 --- a/obd/__init__.py +++ b/obd/__init__.py @@ -44,7 +44,6 @@ from .OBDResponse import OBDResponse, Unit from .protocols import ECU from .utils import scan_serial, scanSerial, OBDStatus # TODO: scanSerial() deprecated -from .debug import debug import logging diff --git a/obd/elm327.py b/obd/elm327.py index a8faa899..bdad95d0 100644 --- a/obd/elm327.py +++ b/obd/elm327.py @@ -32,10 +32,11 @@ import re import serial import time +import logging from .protocols import * from .utils import OBDStatus -from .debug import debug +logger = logging.getLogger(__name__) class ELM327: @@ -109,14 +110,14 @@ def __init__(self, portname, baudrate, protocol): # ------------- open port ------------- try: - debug("Opening serial port '%s'" % portname) + logger.info("Opening serial port '%s'" % portname) self.__port = serial.Serial(portname, \ baudrate = baudrate, \ parity = serial.PARITY_NONE, \ stopbits = 1, \ bytesize = 8, \ timeout = 3) # seconds - debug("Serial port successfully opened on " + self.port_name()) + logger.info("Serial port successfully opened on " + self.port_name()) except serial.SerialException as e: self.__error(e) @@ -158,16 +159,16 @@ def __init__(self, portname, baudrate, protocol): # try to communicate with the car, and load the correct protocol parser if self.load_protocol(protocol): self.__status = OBDStatus.CAR_CONNECTED - debug("Connection successful") + logger.info("Connection successful") else: - debug("Connected to the adapter, but failed to connect to the vehicle", True) + logger.error("Connected to the adapter, but failed to connect to the vehicle") def load_protocol(self, protocol): if protocol is not None: # an explicit protocol was specified if protocol not in self._SUPPORTED_PROTOCOLS: - debug("%s is not a valid protocol. Please use \"1\" through \"A\"", True) + logger.error("%s is not a valid protocol. Please use \"1\" through \"A\"") return False return self.manual_protocol(protocol) else: @@ -207,7 +208,7 @@ def auto_protocol(self): # ------------------- ATDPN (list protocol number) ------------------- r = self.__send(b"ATDPN") if len(r) != 1: - debug("Failed to retrieve current protocol", True) + logger.error("Failed to retrieve current protocol") return False @@ -224,7 +225,7 @@ def auto_protocol(self): # an unknown protocol # this is likely because not all adapter/car combinations work # in "auto" mode. Some respond to ATDPN responded with "0" - debug("ELM responded with unknown protocol. Trying them one-by-one") + logger.info("ELM responded with unknown protocol. Trying them one-by-one") for p in self._TRY_PROTOCOL_ORDER: r = self.__send(b"ATTP" + p.encode()) @@ -287,13 +288,13 @@ def __has_message(self, lines, text): def __error(self, msg=None): - """ handles fatal failures, print debug info and closes serial """ + """ handles fatal failures, print logger.info info and closes serial """ self.close() - debug("Connection Error:", True) + logger.error("Connection Error:") if msg is not None: - debug(' ' + str(msg), True) + logger.error(str(msg)) def port_name(self): @@ -347,7 +348,7 @@ def send_and_parse(self, cmd): """ if self.__status == OBDStatus.NOT_CONNECTED: - debug("cannot send_and_parse() when unconnected", True) + logger.info("cannot send_and_parse() when unconnected") return None lines = self.__send(cmd) @@ -367,7 +368,7 @@ def __send(self, cmd, delay=None): self.__write(cmd) if delay is not None: - debug("wait: %d seconds" % delay) + logger.info("wait: %d seconds" % delay) time.sleep(delay) return self.__read() @@ -380,12 +381,12 @@ def __write(self, cmd): if self.__port: cmd += b"\r\n" # terminate - debug("write: " + repr(cmd)) + logger.debug("write: " + repr(cmd)) self.__port.flushInput() # dump everything in the input buffer self.__port.write(cmd) # turn the string into bytes and write self.__port.flush() # wait for the output buffer to finish transmitting else: - debug("cannot perform __write() when unconnected", True) + logger.info("cannot perform __write() when unconnected") def __read(self): @@ -407,10 +408,10 @@ def __read(self): if not c: if attempts <= 0: - debug("Failed to read port, giving up") + logger.info("Failed to read port, giving up") break - debug("Failed to read port, trying again...") + logger.info("Failed to read port, trying again...") attempts -= 1 continue @@ -424,10 +425,10 @@ def __read(self): buffer += c # whatever is left must be part of the response else: - debug("cannot perform __read() when unconnected", True) + logger.info("cannot perform __read() when unconnected") return "" - debug("read: " + repr(buffer)) + logger.debug("read: " + repr(buffer)) # convert bytes into a standard string raw = buffer.decode() From b91d9d56b9e93b4c7a32dd5d70073b6ab415e9dd Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 29 Jun 2016 00:23:11 -0400 Subject: [PATCH 006/128] added logging to remaining files --- obd/OBDCommand.py | 11 +++++++++-- obd/async.py | 33 ++++++++++++++++++--------------- obd/commands.py | 14 ++++++++------ obd/decoders.py | 26 +++++++++++++++----------- obd/obd.py | 3 +-- obd/utils.py | 7 +++++-- 6 files changed, 56 insertions(+), 38 deletions(-) diff --git a/obd/OBDCommand.py b/obd/OBDCommand.py index 65a365e5..967ec9ec 100644 --- a/obd/OBDCommand.py +++ b/obd/OBDCommand.py @@ -30,10 +30,13 @@ ######################################################################## from .utils import * -from .debug import debug from .protocols import ECU from .OBDResponse import OBDResponse +import logging + +logger = logging.getLogger(__name__) + class OBDCommand(): def __init__(self, @@ -78,7 +81,7 @@ def pid_int(self): # TODO: remove later @property def supported(self): - debug("OBDCommand.supported is deprecated. Use OBD.supports() instead", True) + logger.warning("OBDCommand.supported is deprecated. Use OBD.supports() instead") return False def __call__(self, messages): @@ -96,6 +99,8 @@ def __call__(self, messages): r = OBDResponse(self, messages) if messages: r.value, r.unit = self.decode(messages) + else: + logger.info(str(self) + " did not recieve any acceptable messages") return r @@ -106,9 +111,11 @@ def __constrain_message_data(self, message): if len(message.data) > self.bytes: # chop off the right side message.data = message.data[:self.bytes] + logger.debug("Message was longer than expected. Trimmed message: " + repr(message.data)) else: # pad the right with zeros message.data += (b'\x00' * (self.bytes - len(message.data))) + logger.debug("Message was shorter than expected. Padded message: " + repr(message.data)) def __str__(self): diff --git a/obd/async.py b/obd/async.py index 264600da..9ea67b40 100644 --- a/obd/async.py +++ b/obd/async.py @@ -31,9 +31,12 @@ import time import threading +import logging from .OBDResponse import OBDResponse -from .debug import debug -from . import OBD +from .obd import OBD + +logger = logging.getLogger(__name__) + class Async(OBD): """ @@ -58,15 +61,15 @@ def running(self): def start(self): """ Starts the async update loop """ if not self.is_connected(): - debug("Async thread not started because no connection was made") + logger.info("Async thread not started because no connection was made") return if len(self.__commands) == 0: - debug("Async thread not started because no commands were registered") + logger.info("Async thread not started because no commands were registered") return if self.__thread is None: - debug("Starting async thread") + logger.info("Starting async thread") self.__running = True self.__thread = threading.Thread(target=self.run) self.__thread.daemon = True @@ -76,11 +79,11 @@ def start(self): def stop(self): """ Stops the async update loop """ if self.__thread is not None: - debug("Stopping async thread...") + logger.info("Stopping async thread...") self.__running = False self.__thread.join() self.__thread = None - debug("Async thread stopped") + logger.info("Async thread stopped") def paused(self): @@ -130,22 +133,22 @@ def watch(self, c, callback=None, force=False): # the dict shouldn't be changed while the daemon thread is iterating if self.__running: - debug("Can't watch() while running, please use stop()", True) + logger.warning("Can't watch() while running, please use stop()") else: if not force and not self.supports(c): - debug("'%s' is not supported" % str(c), True) + logger.warning("'%s' is not supported" % str(c)) return # new command being watched, store the command if c not in self.__commands: - debug("Watching command: %s" % str(c)) + logger.info("Watching command: %s" % str(c)) self.__commands[c] = OBDResponse() # give it an initial value self.__callbacks[c] = [] # create an empty list # if a callback was given, push it if hasattr(callback, "__call__") and (callback not in self.__callbacks[c]): - debug("subscribing callback for command: %s" % str(c)) + logger.info("subscribing callback for command: %s" % str(c)) self.__callbacks[c].append(callback) @@ -158,9 +161,9 @@ def unwatch(self, c, callback=None): # the dict shouldn't be changed while the daemon thread is iterating if self.__running: - debug("Can't unwatch() while running, please use stop()", True) + logger.warning("Can't unwatch() while running, please use stop()") else: - debug("Unwatching command: %s" % str(c)) + logger.info("Unwatching command: %s" % str(c)) if c in self.__commands: # if a callback was specified, only remove the callback @@ -181,9 +184,9 @@ def unwatch_all(self): # the dict shouldn't be changed while the daemon thread is iterating if self.__running: - debug("Can't unwatch_all() while running, please use stop()", True) + logger.warning("Can't unwatch_all() while running, please use stop()") else: - debug("Unwatching all") + logger.info("Unwatching all") self.__commands = {} self.__callbacks = {} diff --git a/obd/commands.py b/obd/commands.py index 1789f3dd..b914fc61 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -32,8 +32,10 @@ from .protocols import ECU from .OBDCommand import OBDCommand from .decoders import * -from .debug import debug +import logging + +logger = logging.getLogger(__name__) @@ -235,7 +237,7 @@ def __getitem__(self, key): elif isinstance(key, str) or isinstance(key, unicode): return self.__dict__[key] else: - debug("OBD commands can only be retrieved by PID value or dict name", True) + logger.warning("OBD commands can only be retrieved by PID value or dict name") def __len__(self): @@ -282,7 +284,7 @@ def set_supported(self, mode, pid, v): if self.has(mode, pid): self.modes[mode][pid].supported = v else: - debug("set_supported() only accepts boolean values", True) + logger.warning("set_supported() only accepts boolean values") def has_command(self, c): @@ -290,7 +292,7 @@ def has_command(self, c): if isinstance(c, OBDCommand): return c in self.__dict__.values() else: - debug("has_command() only accepts OBDCommand objects", True) + logger.warning("has_command() only accepts OBDCommand objects") return False @@ -299,7 +301,7 @@ def has_name(self, s): if isinstance(s, str) or isinstance(s, unicode): return s.isupper() and (s in self.__dict__.keys()) else: - debug("has_name() only accepts string names for commands", True) + logger.warning("has_name() only accepts string names for commands") return False @@ -314,7 +316,7 @@ def has_pid(self, mode, pid): return False return True else: - debug("has_pid() only accepts integer values for mode and PID", True) + logger.warning("has_pid() only accepts integer values for mode and PID") return False diff --git a/obd/decoders.py b/obd/decoders.py index f6767b1a..2df9a25e 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -32,9 +32,13 @@ import math from .utils import * from .codes import * -from .debug import debug from .OBDResponse import Unit, Status, Test +import logging + +logger = logging.getLogger(__name__) + + ''' All decoders take the form: @@ -248,7 +252,7 @@ def elm_voltage(messages): try: return (float(v), Unit.VOLT) except ValueError: - debug("Failed to parse ELM voltage", True) + logger.warning("Failed to parse ELM voltage") return (None, Unit.NONE) @@ -281,7 +285,7 @@ def status(messages): bitToBool(bits[9]))) - # different tests for different ignition types + # different tests for different ignition types if(output.ignition_type == IGNITION_TYPE[0]): # spark for i in range(8): if SPARK_TESTS[i] is not None: @@ -299,7 +303,7 @@ def status(messages): t = Test(COMPRESSION_TESTS[i], \ bitToBool(bits[(2 * 8) + i]), \ bitToBool(bits[(3 * 8) + i])) - + output.tests.append(t) return (output, Unit.NONE) @@ -311,19 +315,19 @@ def fuel_status(messages): v = d[0] # todo, support second fuel system if v <= 0: - debug("Invalid fuel status response (v <= 0)", True) + logger.warning("Invalid fuel status response (v <= 0)") return (None, Unit.NONE) i = math.log(v, 2) # only a single bit should be on if i % 1 != 0: - debug("Invalid fuel status response (multiple bits set)", True) + logger.warning("Invalid fuel status response (multiple bits set)") return (None, Unit.NONE) i = int(i) if i >= len(FUEL_STATUS): - debug("Invalid fuel status response (no table entry)", True) + logger.warning("Invalid fuel status response (no table entry)") return (None, Unit.NONE) return (FUEL_STATUS[i], Unit.NONE) @@ -334,19 +338,19 @@ def air_status(messages): v = d[0] if v <= 0: - debug("Invalid air status response (v <= 0)", True) + logger.warning("Invalid air status response (v <= 0)") return (None, Unit.NONE) i = math.log(v, 2) # only a single bit should be on if i % 1 != 0: - debug("Invalid air status response (multiple bits set)", True) + logger.warning("Invalid air status response (multiple bits set)") return (None, Unit.NONE) i = int(i) if i >= len(AIR_STATUS): - debug("Invalid air status response (no table entry)", True) + logger.warning("Invalid air status response (no table entry)") return (None, Unit.NONE) return (AIR_STATUS[i], Unit.NONE) @@ -361,7 +365,7 @@ def obd_compliance(_hex): if i < len(OBD_COMPLIANCE): v = OBD_COMPLIANCE[i] - return (v, Unit.NONE) + return (v, Unit.NONE) def fuel_type(_hex): diff --git a/obd/obd.py b/obd/obd.py index 3811b7b6..a3004f73 100644 --- a/obd/obd.py +++ b/obd/obd.py @@ -37,7 +37,6 @@ from .commands import commands from .OBDResponse import OBDResponse from .utils import scan_serial, OBDStatus -from .debug import debug logger = logging.getLogger(__name__) @@ -183,7 +182,7 @@ def protocol_id(self): def get_port_name(self): # TODO: deprecated, remove later - print("OBD.get_port_name() is deprecated, use OBD.port_name() instead") + logger.warning("OBD.get_port_name() is deprecated, use OBD.port_name() instead") return self.port_name() diff --git a/obd/utils.py b/obd/utils.py index ea3b3cd3..015bc9bb 100644 --- a/obd/utils.py +++ b/obd/utils.py @@ -34,7 +34,10 @@ import string import glob import sys -from .debug import debug +import logging + +logger = logging.getLogger(__name__) + class OBDStatus: @@ -161,5 +164,5 @@ def scan_serial(): # TODO: deprecated, remove later def scanSerial(): - print("scanSerial() is deprecated, use scan_serial() instead") + logger.warning("scanSerial() is deprecated, use scan_serial() instead") return scan_serial() From c496b35e87f0e412a8f7832c0bc8465f223b4a1e Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 29 Jun 2016 00:35:41 -0400 Subject: [PATCH 007/128] added logging to protocol files --- obd/protocols/protocol.py | 5 ++++- obd/protocols/protocol_can.py | 26 +++++++++++++++----------- obd/protocols/protocol_legacy.py | 20 ++++++++++++-------- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/obd/protocols/protocol.py b/obd/protocols/protocol.py index efe83a9a..01c4b547 100644 --- a/obd/protocols/protocol.py +++ b/obd/protocols/protocol.py @@ -31,7 +31,10 @@ from binascii import hexlify from obd.utils import isHex, num_bits_set -from obd.debug import debug + +import logging + +logger = logging.getLogger(__name__) """ diff --git a/obd/protocols/protocol_can.py b/obd/protocols/protocol_can.py index f02aa35c..d4220ec9 100644 --- a/obd/protocols/protocol_can.py +++ b/obd/protocols/protocol_can.py @@ -31,7 +31,11 @@ from binascii import unhexlify from obd.utils import contiguous -from .protocol import * +from .protocol import Protocol, Message, Frame, ECU + +import logging + +logger = logging.getLogger(__name__) class CANProtocol(Protocol): @@ -66,7 +70,7 @@ def parse_frame(self, frame): # Handle odd size frames and drop if len(raw) & 1: - debug("Dropping frame for being odd") + logger.debug("Dropping frame for being odd") return False raw_bytes = bytearray(unhexlify(raw)) @@ -79,11 +83,11 @@ def parse_frame(self, frame): # # 00 00 07 E8 10 20 ... - debug("Dropped frame for being too short") + logger.debug("Dropped frame for being too short") return False if len(raw_bytes) > 12: - debug("Dropped frame for being too long") + logger.debug("Dropped frame for being too long") return False @@ -127,7 +131,7 @@ def parse_frame(self, frame): if frame.type not in [self.FRAME_TYPE_SF, self.FRAME_TYPE_FF, self.FRAME_TYPE_CF]: - debug("Dropping frame carrying unknown PCI frame type") + logger.debug("Dropping frame carrying unknown PCI frame type") return False @@ -169,7 +173,7 @@ def parse_message(self, message): frame = frames[0] if frame.type != self.FRAME_TYPE_SF: - debug("Recieved lone frame not marked as single frame") + logger.debug("Recieved lone frame not marked as single frame") return False # extract data, ignore PCI byte and anything after the marked length @@ -190,19 +194,19 @@ def parse_message(self, message): elif f.type == self.FRAME_TYPE_CF: cf.append(f) else: - debug("Dropping frame in multi-frame response not marked as FF or CF") + logger.debug("Dropping frame in multi-frame response not marked as FF or CF") # check that we captured only one first-frame if len(ff) > 1: - debug("Recieved multiple frames marked FF") + logger.debug("Recieved multiple frames marked FF") return False elif len(ff) == 0: - debug("Never received frame marked FF") + logger.debug("Never received frame marked FF") return False # check that there was at least one consecutive-frame if len(cf) == 0: - debug("Never received frame marked CF") + logger.debug("Never received frame marked CF") return False # calculate proper sequence indices from the lower 4 bits given @@ -225,7 +229,7 @@ def parse_message(self, message): # check contiguity, and that we aren't missing any frames indices = [f.seq_index for f in cf] if not contiguous(indices, 1, len(cf)): - debug("Recieved multiline response with missing frames") + logger.debug("Recieved multiline response with missing frames") return False diff --git a/obd/protocols/protocol_legacy.py b/obd/protocols/protocol_legacy.py index 22abb137..1e24eeea 100644 --- a/obd/protocols/protocol_legacy.py +++ b/obd/protocols/protocol_legacy.py @@ -31,7 +31,11 @@ from binascii import unhexlify from obd.utils import contiguous -from .protocol import * +from .protocol import Protocol, Message, Frame, ECU + +import logging + +logger = logging.getLogger(__name__) class LegacyProtocol(Protocol): @@ -49,17 +53,17 @@ def parse_frame(self, frame): # Handle odd size frames and drop if len(raw) & 1: - debug("Dropping frame for being odd") + logger.debug("Dropping frame for being odd") return False raw_bytes = bytearray(unhexlify(raw)) if len(raw_bytes) < 6: - debug("Dropped frame for being too short") + logger.debug("Dropped frame for being too short") return False if len(raw_bytes) > 11: - debug("Dropped frame for being too long") + logger.debug("Dropped frame for being too long") return False # Ex. @@ -84,15 +88,15 @@ def parse_message(self, message): # len(frames) will always be >= 1 (see the caller, protocol.py) mode = frames[0].data[0] - + # test that all frames are responses to the same Mode (SID) if len(frames) > 1: if not all([mode == f.data[0] for f in frames[1:]]): - debug("Recieved frames from multiple commands") + logger.debug("Recieved frames from multiple commands") return False # legacy protocols have different re-assembly - # procedures for different Modes + # procedures for different Modes if mode == 0x43: # GET_DTC requests return frames with no PID or order bytes @@ -134,7 +138,7 @@ def parse_message(self, message): # check contiguity indices = [f.data[2] for f in frames] if not contiguous(indices, 1, len(frames)): - debug("Recieved multiline response with missing frames") + logger.debug("Recieved multiline response with missing frames") return False # now that they're in order, accumulate the data from each frame From 3e0d3212882abaea36d87b52bc12c41153cd3fa7 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 29 Jun 2016 00:40:41 -0400 Subject: [PATCH 008/128] removed old debug system, wrote docs --- docs/Debug.md | 14 ++++++++------ obd/__init__.py | 2 +- obd/debug.py | 50 ------------------------------------------------- 3 files changed, 9 insertions(+), 57 deletions(-) delete mode 100644 obd/debug.py diff --git a/docs/Debug.md b/docs/Debug.md index 0cacdb7c..ea7fd9dc 100644 --- a/docs/Debug.md +++ b/docs/Debug.md @@ -1,16 +1,18 @@ -python-OBD also contains a debug object that receives status messages and errors. Console printing is disabled by default, but can be enabled manually. A custom debug handler can also be set. +python-OBD uses python's builtin logging system. By default, it is setup to send output to `stderr` with a level of WARNING. The module's logger can be accessed via the `logger` variable at the root of the module. For instance, to enable console printing of all debug messages, use the following snippet: ```python import obd +import logging -obd.debug.console = True +obd.logger.setLevel(logging.DEBUG) +``` -# AND / OR +Or, to silence all logging output from python-OBD: -def log(msg): - print msg +```python +import obd -obd.debug.handler = log +obd.logger.removeHandler(obd.console_handler) ``` --- diff --git a/obd/__init__.py b/obd/__init__.py index 582956c9..1e7851f4 100644 --- a/obd/__init__.py +++ b/obd/__init__.py @@ -48,7 +48,7 @@ import logging logger = logging.getLogger(__name__) -logger.setLevel(logging.INFO) +logger.setLevel(logging.WARNING) console_handler = logging.StreamHandler() # sends output to stderr console_handler.setFormatter(logging.Formatter("[%(name)s] %(message)s")) diff --git a/obd/debug.py b/obd/debug.py deleted file mode 100644 index 06105697..00000000 --- a/obd/debug.py +++ /dev/null @@ -1,50 +0,0 @@ - -######################################################################## -# # -# python-OBD: A python OBD-II serial module derived from pyobd # -# # -# Copyright 2004 Donour Sizemore (donour@uchicago.edu) # -# Copyright 2009 Secons Ltd. (www.obdtester.com) # -# Copyright 2009 Peter J. Creath # -# Copyright 2016 Brendan Whitfield (brendan-w.com) # -# # -######################################################################## -# # -# debug.py # -# # -# This file is part of python-OBD (a derivative of pyOBD) # -# # -# python-OBD is free software: you can redistribute it and/or modify # -# it under the terms of the GNU General Public License as published by # -# the Free Software Foundation, either version 2 of the License, or # -# (at your option) any later version. # -# # -# python-OBD is distributed in the hope that it will be useful, # -# but WITHOUT ANY WARRANTY; without even the implied warranty of # -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # -# GNU General Public License for more details. # -# # -# You should have received a copy of the GNU General Public License # -# along with python-OBD. If not, see . # -# # -######################################################################## - -class Debug(): - def __init__(self): - self.console = False - self.handler = None - - def __call__(self, msg, forcePrint=False): - - if self.console or forcePrint: - print("[obd] " + str(msg)) - - if hasattr(self.handler, '__call__'): - self.handler(msg) - -debug = Debug() - - -class ProtocolError(Exception): - def __init__(self): - pass From 44d03cced3c3ad40f164607e62a400b4d6bca344 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 29 Jun 2016 00:41:33 -0400 Subject: [PATCH 009/128] fixed logging note on troubleshooting page --- docs/Troubleshooting.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Troubleshooting.md b/docs/Troubleshooting.md index 716d3f3d..317793ac 100644 --- a/docs/Troubleshooting.md +++ b/docs/Troubleshooting.md @@ -4,7 +4,8 @@ If python-OBD is not working properly, the first thing you should do is enable debug output. Add the following line before your connection code to print all of the debug information to your console: ```python -obd.debug.console = True +import logging +obd.logger.setLevel(logging.DEBUG) ``` Here are some common logs from python-OBD, and their meanings: From c0c4f25e723a7477bf809c18c7cfbc07cacd7db9 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 29 Jun 2016 00:44:24 -0400 Subject: [PATCH 010/128] organized command list --- obd/commands.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/obd/commands.py b/obd/commands.py index b914fc61..036e4528 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -66,6 +66,8 @@ OBDCommand("SPEED" , "Vehicle Speed" , b"010D", 1, speed, ECU.ENGINE, True), OBDCommand("TIMING_ADVANCE" , "Timing Advance" , b"010E", 1, timing_advance, ECU.ENGINE, True), OBDCommand("INTAKE_TEMP" , "Intake Air Temp" , b"010F", 1, temp, ECU.ENGINE, True), + + # name description cmd bytes decoder ECU fast OBDCommand("MAF" , "Air Flow Rate (MAF)" , b"0110", 2, maf, ECU.ENGINE, True), OBDCommand("THROTTLE_POS" , "Throttle Position" , b"0111", 1, percent, ECU.ENGINE, True), OBDCommand("AIR_STATUS" , "Secondary Air Status" , b"0112", 1, air_status, ECU.ENGINE, True), @@ -100,6 +102,8 @@ OBDCommand("EGR_ERROR" , "EGR Error" , b"012D", 1, percent_centered, ECU.ENGINE, True), OBDCommand("EVAPORATIVE_PURGE" , "Commanded Evaporative Purge" , b"012E", 1, percent, ECU.ENGINE, True), OBDCommand("FUEL_LEVEL" , "Fuel Level Input" , b"012F", 1, percent, ECU.ENGINE, True), + + # name description cmd bytes decoder ECU fast OBDCommand("WARMUPS_SINCE_DTC_CLEAR" , "Number of warm-ups since codes cleared" , b"0130", 1, count, ECU.ENGINE, True), OBDCommand("DISTANCE_SINCE_DTC_CLEAR" , "Distance traveled since codes cleared" , b"0131", 2, distance, ECU.ENGINE, True), OBDCommand("EVAP_VAPOR_PRESSURE" , "Evaporative system vapor pressure" , b"0132", 2, evap_pressure, ECU.ENGINE, True), @@ -134,6 +138,8 @@ OBDCommand("RUN_TIME_MIL" , "Time run with MIL on" , b"014D", 2, minutes, ECU.ENGINE, True), OBDCommand("TIME_SINCE_DTC_CLEARED" , "Time since trouble codes cleared" , b"014E", 2, minutes, ECU.ENGINE, True), OBDCommand("MAX_VALUES" , "Various Max values" , b"014F", 4, drop, ECU.ENGINE, True), # todo: decode this + + # name description cmd bytes decoder ECU fast OBDCommand("MAX_MAF" , "Maximum value for mass air flow sensor" , b"0150", 4, max_maf, ECU.ENGINE, True), OBDCommand("FUEL_TYPE" , "Fuel Type" , b"0151", 1, fuel_type, ECU.ENGINE, True), OBDCommand("ETHANOL_PERCENT" , "Ethanol Fuel Percent" , b"0152", 1, percent, ECU.ENGINE, True), From 89fb37908e41226ebd5f9d1313f786e6138b1d50 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 29 Jun 2016 15:18:28 -0400 Subject: [PATCH 011/128] confirmed CAN DTC count byte --- obd/protocols/protocol_can.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/obd/protocols/protocol_can.py b/obd/protocols/protocol_can.py index d4220ec9..ab1f34f7 100644 --- a/obd/protocols/protocol_can.py +++ b/obd/protocols/protocol_can.py @@ -268,7 +268,9 @@ def parse_message(self, message): mode = message.data[0] if mode == 0x43: - # TODO: confirm this logic. I don't have any raw test data for it yet + # [] + # 43 03 11 11 22 22 33 33 + # [DTC] [DTC] [DTC] # fetch the DTC count, and use it as a length code num_dtc_bytes = message.data[1] * 2 From 4a3b0c2ea7e3a6a06579bab246e3d974a81ec5e2 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 29 Jun 2016 16:25:39 -0400 Subject: [PATCH 012/128] added debug output for the ECU map --- obd/protocols/protocol.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/obd/protocols/protocol.py b/obd/protocols/protocol.py index 01c4b547..d0c102ec 100644 --- a/obd/protocols/protocol.py +++ b/obd/protocols/protocol.py @@ -149,6 +149,11 @@ def __init__(self, lines_0100): # subsequent runs will now be tagged correctly self.populate_ecu_map(messages) + # log out the ecu map + for tx_id, ecu in self.ecu_map.items(): + names = [k for k in ECU.__dict__ if ECU.__dict__[k] == ecu ] + logger.debug("Chose ECU %d as %s" % (tx_id, names)) + def __call__(self, lines): """ From 87901482df4ea17e670a31797b58d637657f4942 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 29 Jun 2016 16:55:11 -0400 Subject: [PATCH 013/128] implemented units using pint --- obd/OBDCommand.py | 2 +- obd/OBDResponse.py | 37 ++++---------------- obd/decoders.py | 86 +++++++++++++++++++++++----------------------- setup.py | 4 +-- 4 files changed, 53 insertions(+), 76 deletions(-) diff --git a/obd/OBDCommand.py b/obd/OBDCommand.py index 967ec9ec..0fc023ab 100644 --- a/obd/OBDCommand.py +++ b/obd/OBDCommand.py @@ -98,7 +98,7 @@ def __call__(self, messages): # and reference to original command r = OBDResponse(self, messages) if messages: - r.value, r.unit = self.decode(messages) + r.value = self.decode(messages) else: logger.info(str(self) + " did not recieve any acceptable messages") diff --git a/obd/OBDResponse.py b/obd/OBDResponse.py index 59148f72..d9f6c600 100644 --- a/obd/OBDResponse.py +++ b/obd/OBDResponse.py @@ -32,33 +32,14 @@ import time +import pint - -class Unit: - """ All unit constants used in python-OBD """ - - NONE = None - RATIO = "Ratio" - COUNT = "Count" - PERCENT = "%" - RPM = "RPM" - VOLT = "Volt" - F = "F" - C = "C" - SEC = "Second" - MIN = "Minute" - PA = "Pa" - KPA = "kPa" - PSI = "psi" - KPH = "kph" - MPH = "mph" - DEGREES = "Degrees" - GPS = "Grams per Second" - MA = "mA" - KM = "km" - LPH = "Liters per Hour" - +# export the unit registry +Unit = pint.UnitRegistry() +Unit.define("percent = [] = %") +Unit.define("gps = gram / second = GPS = grams_per_second") +Unit.define("lph = liter / hour = LPH = liters_per_hour") class OBDResponse(): @@ -68,17 +49,13 @@ def __init__(self, command=None, messages=None): self.command = command self.messages = messages if messages else [] self.value = None - self.unit = Unit.NONE self.time = time.time() def is_null(self): return (not self.messages) or (self.value == None) def __str__(self): - if self.unit != Unit.NONE: - return "%s %s" % (str(self.value), str(self.unit)) - else: - return str(self.value) + return str(self.value) diff --git a/obd/decoders.py b/obd/decoders.py index 2df9a25e..41a9d125 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -51,23 +51,23 @@ def (): # drop all messages, return None def drop(messages): - return (None, Unit.NONE) + return None # data in, data out def noop(messages): - return (messages[0].data, Unit.NONE) + return messages[0].data # hex in, bitstring out def pid(messages): d = messages[0].data v = bytes_to_bits(d) - return (v, Unit.NONE) + return v # returns the raw strings from the ELM def raw_string(messages): - return ("\n".join([m.raw() for m in messages]), Unit.NONE) + return "\n".join([m.raw() for m in messages]) ''' Sensor decoders @@ -77,83 +77,83 @@ def raw_string(messages): def count(messages): d = messages[0].data v = bytes_to_int(d) - return (v, Unit.COUNT) + return v * Unit.count # 0 to 100 % def percent(messages): d = messages[0].data v = d[0] v = v * 100.0 / 255.0 - return (v, Unit.PERCENT) + return v * Unit.percent # -100 to 100 % def percent_centered(messages): d = messages[0].data v = d[0] v = (v - 128) * 100.0 / 128.0 - return (v, Unit.PERCENT) + return v * Unit.percent # -40 to 215 C def temp(messages): d = messages[0].data v = bytes_to_int(d) v = v - 40 - return (v, Unit.C) + return v * Unit.celsius # -40 to 6513.5 C def catalyst_temp(messages): d = messages[0].data v = bytes_to_int(d) v = (v / 10.0) - 40 - return (v, Unit.C) + return v * Unit.celsius # -128 to 128 mA def current_centered(messages): d = messages[0].data v = bytes_to_int(d[2:4]) v = (v / 256.0) - 128 - return (v, Unit.MA) + return v * Unit.milliampere # 0 to 1.275 volts def sensor_voltage(messages): d = messages[0].data v = d[0] v = v / 200.0 - return (v, Unit.VOLT) + return v * Unit.volt # 0 to 8 volts def sensor_voltage_big(messages): d = messages[0].data v = bytes_to_int(d[2:4]) v = (v * 8.0) / 65535 - return (v, Unit.VOLT) + return v * Unit.volt # 0 to 765 kPa def fuel_pressure(messages): d = messages[0].data v = d[0] v = v * 3 - return (v, Unit.KPA) + return v * Unit.kilopascal # 0 to 255 kPa def pressure(messages): d = messages[0].data v = d[0] - return (v, Unit.KPA) + return v * Unit.kilopascal # 0 to 5177 kPa def fuel_pres_vac(messages): d = messages[0].data v = bytes_to_int(d) v = v * 0.079 - return (v, Unit.KPA) + return v * Unit.kilopascal # 0 to 655,350 kPa def fuel_pres_direct(messages): d = messages[0].data v = bytes_to_int(d) v = v * 10 - return (v, Unit.KPA) + return v * Unit.kilopascal # -8192 to 8192 Pa def evap_pressure(messages): @@ -162,86 +162,86 @@ def evap_pressure(messages): a = twos_comp(unhex(d[0]), 8) b = twos_comp(unhex(d[1]), 8) v = ((a * 256.0) + b) / 4.0 - return (v, Unit.PA) + return v * Unit.pascal # 0 to 327.675 kPa def abs_evap_pressure(messages): d = messages[0].data v = bytes_to_int(d) v = v / 200.0 - return (v, Unit.KPA) + return v * Unit.kilopascal # -32767 to 32768 Pa def evap_pressure_alt(messages): d = messages[0].data v = bytes_to_int(d) v = v - 32767 - return (v, Unit.PA) + return v * Unit.pascal # 0 to 16,383.75 RPM def rpm(messages): d = messages[0].data v = bytes_to_int(d) / 4.0 - return (v, Unit.RPM) + return v * Unit.rpm # 0 to 255 KPH def speed(messages): d = messages[0].data v = bytes_to_int(d) - return (v, Unit.KPH) + return v * Unit.kph # -64 to 63.5 degrees def timing_advance(messages): d = messages[0].data v = d[0] v = (v - 128) / 2.0 - return (v, Unit.DEGREES) + return v * Unit.degree # -210 to 301 degrees def inject_timing(messages): d = messages[0].data v = bytes_to_int(d) v = (v - 26880) / 128.0 - return (v, Unit.DEGREES) + return v * Unit.degree # 0 to 655.35 grams/sec def maf(messages): d = messages[0].data v = bytes_to_int(d) v = v / 100.0 - return (v, Unit.GPS) + return v * Unit.gps # 0 to 2550 grams/sec def max_maf(messages): d = messages[0].data v = d[0] v = v * 10 - return (v, Unit.GPS) + return v * Unit.gps # 0 to 65535 seconds def seconds(messages): d = messages[0].data v = bytes_to_int(d) - return (v, Unit.SEC) + return v * Unit.second # 0 to 65535 minutes def minutes(messages): d = messages[0].data v = bytes_to_int(d) - return (v, Unit.MIN) + return v * Unit.minute # 0 to 65535 km def distance(messages): d = messages[0].data v = bytes_to_int(d) - return (v, Unit.KM) + return v * Unit.kilometer # 0 to 3212 Liters/hour def fuel_rate(messages): d = messages[0].data v = bytes_to_int(d) v = v * 0.05 - return (v, Unit.LPH) + return v * Unit.liters_per_hour def elm_voltage(messages): @@ -250,10 +250,10 @@ def elm_voltage(messages): v = messages[0].frames[0].raw try: - return (float(v), Unit.VOLT) + return float(v) * Unit.volt except ValueError: logger.warning("Failed to parse ELM voltage") - return (None, Unit.NONE) + return None ''' @@ -306,7 +306,7 @@ def status(messages): output.tests.append(t) - return (output, Unit.NONE) + return output @@ -316,21 +316,21 @@ def fuel_status(messages): if v <= 0: logger.warning("Invalid fuel status response (v <= 0)") - return (None, Unit.NONE) + return None i = math.log(v, 2) # only a single bit should be on if i % 1 != 0: logger.warning("Invalid fuel status response (multiple bits set)") - return (None, Unit.NONE) + return None i = int(i) if i >= len(FUEL_STATUS): logger.warning("Invalid fuel status response (no table entry)") - return (None, Unit.NONE) + return None - return (FUEL_STATUS[i], Unit.NONE) + return FUEL_STATUS[i] def air_status(messages): @@ -339,21 +339,21 @@ def air_status(messages): if v <= 0: logger.warning("Invalid air status response (v <= 0)") - return (None, Unit.NONE) + return None i = math.log(v, 2) # only a single bit should be on if i % 1 != 0: logger.warning("Invalid air status response (multiple bits set)") - return (None, Unit.NONE) + return None i = int(i) if i >= len(AIR_STATUS): logger.warning("Invalid air status response (no table entry)") - return (None, Unit.NONE) + return None - return (AIR_STATUS[i], Unit.NONE) + return AIR_STATUS[i] def obd_compliance(_hex): @@ -365,7 +365,7 @@ def obd_compliance(_hex): if i < len(OBD_COMPLIANCE): v = OBD_COMPLIANCE[i] - return (v, Unit.NONE) + return v def fuel_type(_hex): @@ -377,7 +377,7 @@ def fuel_type(_hex): if i < len(FUEL_TYPES): v = FUEL_TYPES[i] - return (v, Unit.NONE) + return v def single_dtc(_bytes): @@ -424,4 +424,4 @@ def dtc(messages): codes.append( (dtc, desc) ) - return (codes, Unit.NONE) + return codes diff --git a/setup.py b/setup.py index 3b79abdc..21bd121b 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="obd", - version="0.5.0", + version="0.6.0", description=("Serial module for handling live sensor data from a vehicle's OBD-II port"), classifiers=[ "Operating System :: POSIX :: Linux", @@ -25,5 +25,5 @@ packages=find_packages(), include_package_data=True, zip_safe=False, - install_requires=["pyserial"], + install_requires=["pyserial", "pint"], ) From 1d03abf99734233a4f2afe9b65c51a4c2db416e0 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 29 Jun 2016 17:27:31 -0400 Subject: [PATCH 014/128] fixed tests for new Pint units --- obd/decoders.py | 4 +- tests/test_decoders.py | 183 +++++++++++++++++++++-------------------- 2 files changed, 95 insertions(+), 92 deletions(-) diff --git a/obd/decoders.py b/obd/decoders.py index 41a9d125..7f8baa2e 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -98,14 +98,14 @@ def temp(messages): d = messages[0].data v = bytes_to_int(d) v = v - 40 - return v * Unit.celsius + return Unit.Quantity(v, Unit.celsius) # non-multiplicative unit # -40 to 6513.5 C def catalyst_temp(messages): d = messages[0].data v = bytes_to_int(d) v = (v / 10.0) - 40 - return v * Unit.celsius + return Unit.Quantity(v, Unit.celsius) # non-multiplicative unit # -128 to 128 mA def current_centered(messages): diff --git a/tests/test_decoders.py b/tests/test_decoders.py index cd6b783f..dffed7cf 100644 --- a/tests/test_decoders.py +++ b/tests/test_decoders.py @@ -15,9 +15,12 @@ def m(hex_data, frames=[]): return [message] -def float_equals(d1, d2): - values_match = (abs(d1[0] - d2[0]) < 0.02) - units_match = (d1[1] == d2[1]) +FLOAT_EQUALS_TOLERANCE = 0.02 + +# comparison for pint floating point values +def float_equals(va, vb): + units_match = (va.u == vb.u) + values_match = (abs(va.magnitude - vb.magnitude) < FLOAT_EQUALS_TOLERANCE) return values_match and units_match @@ -25,173 +28,173 @@ def float_equals(d1, d2): def test_noop(): - assert d.noop(m("00010203")) == (bytearray([0, 1, 2, 3]), Unit.NONE) + assert d.noop(m("00010203")) == bytearray([0, 1, 2, 3]) def test_drop(): - assert d.drop(m("deadbeef")) == (None, Unit.NONE) + assert d.drop(m("deadbeef")) == None def test_raw_string(): - assert d.raw_string([ Message([]) ]) == ("", Unit.NONE) - assert d.raw_string([ Message([ Frame("NO DATA") ]) ]) == ("NO DATA", Unit.NONE) - assert d.raw_string([ Message([ Frame("A"), Frame("B") ]) ]) == ("A\nB", Unit.NONE) - assert d.raw_string([ Message([ Frame("A") ]), Message([ Frame("B") ]) ]) == ("A\nB", Unit.NONE) + assert d.raw_string([ Message([]) ]) == "" + assert d.raw_string([ Message([ Frame("NO DATA") ]) ]) == "NO DATA" + assert d.raw_string([ Message([ Frame("A"), Frame("B") ]) ]) == "A\nB" + assert d.raw_string([ Message([ Frame("A") ]), Message([ Frame("B") ]) ]) == "A\nB" def test_pid(): - assert d.pid(m("00000000")) == ("00000000000000000000000000000000", Unit.NONE) - assert d.pid(m("F00AA00F")) == ("11110000000010101010000000001111", Unit.NONE) - assert d.pid(m("11")) == ("00010001", Unit.NONE) + assert d.pid(m("00000000")) == "00000000000000000000000000000000" + assert d.pid(m("F00AA00F")) == "11110000000010101010000000001111" + assert d.pid(m("11")) == "00010001" def test_count(): - assert d.count(m("00")) == (0, Unit.COUNT) - assert d.count(m("0F")) == (15, Unit.COUNT) - assert d.count(m("03E8")) == (1000, Unit.COUNT) + assert d.count(m("00")) == 0 * Unit.count + assert d.count(m("0F")) == 15 * Unit.count + assert d.count(m("03E8")) == 1000 * Unit.count def test_percent(): - assert d.percent(m("00")) == (0.0, Unit.PERCENT) - assert d.percent(m("FF")) == (100.0, Unit.PERCENT) + assert d.percent(m("00")) == 0.0 * Unit.percent + assert d.percent(m("FF")) == 100.0 * Unit.percent def test_percent_centered(): - assert d.percent_centered(m("00")) == (-100.0, Unit.PERCENT) - assert d.percent_centered(m("80")) == (0.0, Unit.PERCENT) - assert float_equals(d.percent_centered(m("FF")), (99.2, Unit.PERCENT)) + assert d.percent_centered(m("00")) == -100.0 * Unit.percent + assert d.percent_centered(m("80")) == 0.0 * Unit.percent + assert float_equals(d.percent_centered(m("FF")), 99.2 * Unit.percent) def test_temp(): - assert d.temp(m("00")) == (-40, Unit.C) - assert d.temp(m("FF")) == (215, Unit.C) - assert d.temp(m("03E8")) == (960, Unit.C) + assert d.temp(m("00")) == Unit.Quantity(-40, Unit.celsius) + assert d.temp(m("FF")) == Unit.Quantity(215, Unit.celsius) + assert d.temp(m("03E8")) == Unit.Quantity(960, Unit.celsius) def test_catalyst_temp(): - assert d.catalyst_temp(m("0000")) == (-40.0, Unit.C) - assert d.catalyst_temp(m("FFFF")) == (6513.5, Unit.C) + assert d.catalyst_temp(m("0000")) == Unit.Quantity(-40.0, Unit.celsius) + assert d.catalyst_temp(m("FFFF")) == Unit.Quantity(6513.5, Unit.celsius) def test_current_centered(): - assert d.current_centered(m("00000000")) == (-128.0, Unit.MA) - assert d.current_centered(m("00008000")) == (0.0, Unit.MA) - assert float_equals(d.current_centered(m("0000FFFF")), (128.0, Unit.MA)) - assert d.current_centered(m("ABCD8000")) == (0.0, Unit.MA) # first 2 bytes are unused (should be disregarded) + assert d.current_centered(m("00000000")) == -128.0 * Unit.milliampere + assert d.current_centered(m("00008000")) == 0.0 * Unit.milliampere + assert d.current_centered(m("ABCD8000")) == 0.0 * Unit.milliampere # first 2 bytes are unused (should be disregarded) + assert float_equals(d.current_centered(m("0000FFFF")), 128.0 * Unit.milliampere) def test_sensor_voltage(): - assert d.sensor_voltage(m("0000")) == (0.0, Unit.VOLT) - assert d.sensor_voltage(m("FFFF")) == (1.275, Unit.VOLT) + assert d.sensor_voltage(m("0000")) == 0.0 * Unit.volt + assert d.sensor_voltage(m("FFFF")) == 1.275 * Unit.volt def test_sensor_voltage_big(): - assert d.sensor_voltage_big(m("00000000")) == (0.0, Unit.VOLT) - assert float_equals(d.sensor_voltage_big(m("00008000")), (4.0, Unit.VOLT)) - assert d.sensor_voltage_big(m("0000FFFF")) == (8.0, Unit.VOLT) - assert d.sensor_voltage_big(m("ABCD0000")) == (0.0, Unit.VOLT) # first 2 bytes are unused (should be disregarded) + assert d.sensor_voltage_big(m("00000000")) == 0.0 * Unit.volt + assert float_equals(d.sensor_voltage_big(m("00008000")), 4.0 * Unit.volt) + assert d.sensor_voltage_big(m("0000FFFF")) == 8.0 * Unit.volt + assert d.sensor_voltage_big(m("ABCD0000")) == 0.0 * Unit.volt # first 2 bytes are unused (should be disregarded) def test_fuel_pressure(): - assert d.fuel_pressure(m("00")) == (0, Unit.KPA) - assert d.fuel_pressure(m("80")) == (384, Unit.KPA) - assert d.fuel_pressure(m("FF")) == (765, Unit.KPA) + assert d.fuel_pressure(m("00")) == 0 * Unit.kilopascal + assert d.fuel_pressure(m("80")) == 384 * Unit.kilopascal + assert d.fuel_pressure(m("FF")) == 765 * Unit.kilopascal def test_pressure(): - assert d.pressure(m("00")) == (0, Unit.KPA) - assert d.pressure(m("00")) == (0, Unit.KPA) + assert d.pressure(m("00")) == 0 * Unit.kilopascal + assert d.pressure(m("00")) == 0 * Unit.kilopascal def test_fuel_pres_vac(): - assert d.fuel_pres_vac(m("0000")) == (0.0, Unit.KPA) - assert d.fuel_pres_vac(m("FFFF")) == (5177.265, Unit.KPA) + assert d.fuel_pres_vac(m("0000")) == 0.0 * Unit.kilopascal + assert d.fuel_pres_vac(m("FFFF")) == 5177.265 * Unit.kilopascal def test_fuel_pres_direct(): - assert d.fuel_pres_direct(m("0000")) == (0, Unit.KPA) - assert d.fuel_pres_direct(m("FFFF")) == (655350, Unit.KPA) + assert d.fuel_pres_direct(m("0000")) == 0 * Unit.kilopascal + assert d.fuel_pres_direct(m("FFFF")) == 655350 * Unit.kilopascal def test_evap_pressure(): pass # TODO - #assert d.evap_pressure(m("0000")) == (0.0, Unit.PA) + #assert d.evap_pressure(m("0000")) == 0.0 * Unit.PA) def test_abs_evap_pressure(): - assert d.abs_evap_pressure(m("0000")) == (0, Unit.KPA) - assert d.abs_evap_pressure(m("FFFF")) == (327.675, Unit.KPA) + assert d.abs_evap_pressure(m("0000")) == 0 * Unit.kilopascal + assert d.abs_evap_pressure(m("FFFF")) == 327.675 * Unit.kilopascal def test_evap_pressure_alt(): - assert d.evap_pressure_alt(m("0000")) == (-32767, Unit.PA) - assert d.evap_pressure_alt(m("7FFF")) == (0, Unit.PA) - assert d.evap_pressure_alt(m("FFFF")) == (32768, Unit.PA) + assert d.evap_pressure_alt(m("0000")) == -32767 * Unit.pascal + assert d.evap_pressure_alt(m("7FFF")) == 0 * Unit.pascal + assert d.evap_pressure_alt(m("FFFF")) == 32768 * Unit.pascal def test_rpm(): - assert d.rpm(m("0000")) == (0.0, Unit.RPM) - assert d.rpm(m("FFFF")) == (16383.75, Unit.RPM) + assert d.rpm(m("0000")) == 0.0 * Unit.rpm + assert d.rpm(m("FFFF")) == 16383.75 * Unit.rpm def test_speed(): - assert d.speed(m("00")) == (0, Unit.KPH) - assert d.speed(m("FF")) == (255, Unit.KPH) + assert d.speed(m("00")) == 0 * Unit.kph + assert d.speed(m("FF")) == 255 * Unit.kph def test_timing_advance(): - assert d.timing_advance(m("00")) == (-64.0, Unit.DEGREES) - assert d.timing_advance(m("FF")) == (63.5, Unit.DEGREES) + assert d.timing_advance(m("00")) == -64.0 * Unit.degrees + assert d.timing_advance(m("FF")) == 63.5 * Unit.degrees def test_inject_timing(): - assert d.inject_timing(m("0000")) == (-210, Unit.DEGREES) - assert float_equals(d.inject_timing(m("FFFF")), (302, Unit.DEGREES)) + assert d.inject_timing(m("0000")) == -210 * Unit.degrees + assert float_equals(d.inject_timing(m("FFFF")), 302 * Unit.degrees) def test_maf(): - assert d.maf(m("0000")) == (0.0, Unit.GPS) - assert d.maf(m("FFFF")) == (655.35, Unit.GPS) + assert d.maf(m("0000")) == 0.0 * Unit.grams_per_second + assert d.maf(m("FFFF")) == 655.35 * Unit.grams_per_second def test_max_maf(): - assert d.max_maf(m("00000000")) == (0, Unit.GPS) - assert d.max_maf(m("FF000000")) == (2550, Unit.GPS) - assert d.max_maf(m("00ABCDEF")) == (0, Unit.GPS) # last 3 bytes are unused (should be disregarded) + assert d.max_maf(m("00000000")) == 0 * Unit.grams_per_second + assert d.max_maf(m("FF000000")) == 2550 * Unit.grams_per_second + assert d.max_maf(m("00ABCDEF")) == 0 * Unit.grams_per_second # last 3 bytes are unused (should be disregarded) def test_seconds(): - assert d.seconds(m("0000")) == (0, Unit.SEC) - assert d.seconds(m("FFFF")) == (65535, Unit.SEC) + assert d.seconds(m("0000")) == 0 * Unit.second + assert d.seconds(m("FFFF")) == 65535 * Unit.second def test_minutes(): - assert d.minutes(m("0000")) == (0, Unit.MIN) - assert d.minutes(m("FFFF")) == (65535, Unit.MIN) + assert d.minutes(m("0000")) == 0 * Unit.minute + assert d.minutes(m("FFFF")) == 65535 * Unit.minute def test_distance(): - assert d.distance(m("0000")) == (0, Unit.KM) - assert d.distance(m("FFFF")) == (65535, Unit.KM) + assert d.distance(m("0000")) == 0 * Unit.kilometer + assert d.distance(m("FFFF")) == 65535 * Unit.kilometer def test_fuel_rate(): - assert d.fuel_rate(m("0000")) == (0.0, Unit.LPH) - assert d.fuel_rate(m("FFFF")) == (3276.75, Unit.LPH) + assert d.fuel_rate(m("0000")) == 0.0 * Unit.liters_per_hour + assert d.fuel_rate(m("FFFF")) == 3276.75 * Unit.liters_per_hour def test_fuel_status(): - assert d.fuel_status(m("0100")) == ("Open loop due to insufficient engine temperature", Unit.NONE) - assert d.fuel_status(m("0800")) == ("Open loop due to system failure", Unit.NONE) - assert d.fuel_status(m("0300")) == (None, Unit.NONE) + assert d.fuel_status(m("0100")) == "Open loop due to insufficient engine temperature" + assert d.fuel_status(m("0800")) == "Open loop due to system failure" + assert d.fuel_status(m("0300")) == None def test_air_status(): - assert d.air_status(m("01")) == ("Upstream", Unit.NONE) - assert d.air_status(m("08")) == ("Pump commanded on for diagnostics", Unit.NONE) - assert d.air_status(m("03")) == (None, Unit.NONE) + assert d.air_status(m("01")) == "Upstream" + assert d.air_status(m("08")) == "Pump commanded on for diagnostics" + assert d.air_status(m("03")) == None def test_elm_voltage(): # these aren't parsed as standard hex messages, so manufacture our own - assert d.elm_voltage([ Message([ Frame("12.875") ]) ]) == (12.875, Unit.VOLT) - assert d.elm_voltage([ Message([ Frame("12") ]) ]) == (12, Unit.VOLT) - assert d.elm_voltage([ Message([ Frame("12ABCD") ]) ]) == (None, Unit.NONE) + assert d.elm_voltage([ Message([ Frame("12.875") ]) ]) == 12.875 * Unit.volt + assert d.elm_voltage([ Message([ Frame("12") ]) ]) == 12 * Unit.volt + assert d.elm_voltage([ Message([ Frame("12ABCD") ]) ]) == None def test_dtc(): - assert d.dtc(m("0104")) == ([ + assert d.dtc(m("0104")) == [ ("P0104", "Mass or Volume Air Flow Circuit Intermittent"), - ], Unit.NONE) + ] # multiple codes - assert d.dtc(m("010480034123")) == ([ + assert d.dtc(m("010480034123")) == [ ("P0104", "Mass or Volume Air Flow Circuit Intermittent"), ("B0003", "Unknown error code"), ("C0123", "Unknown error code"), - ], Unit.NONE) + ] # invalid code lengths are dropped - assert d.dtc(m("0104800341")) == ([ + assert d.dtc(m("0104800341")) == [ ("P0104", "Mass or Volume Air Flow Circuit Intermittent"), ("B0003", "Unknown error code"), - ], Unit.NONE) + ] # 0000 codes are dropped - assert d.dtc(m("000001040000")) == ([ + assert d.dtc(m("000001040000")) == [ ("P0104", "Mass or Volume Air Flow Circuit Intermittent"), - ], Unit.NONE) + ] # test multiple messages - assert d.dtc(m("0104") + m("8003") + m("0000")) == ([ + assert d.dtc(m("0104") + m("8003") + m("0000")) == [ ("P0104", "Mass or Volume Air Flow Circuit Intermittent"), ("B0003", "Unknown error code"), - ], Unit.NONE) + ] From 125ea38f918aaa0f8036d1740c50c42bf8ada85b Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 29 Jun 2016 17:30:19 -0400 Subject: [PATCH 015/128] fixed obdsim test for change to Pint quantities --- tests/test_obdsim.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_obdsim.py b/tests/test_obdsim.py index 287ade6c..abf4c429 100644 --- a/tests/test_obdsim.py +++ b/tests/test_obdsim.py @@ -35,9 +35,7 @@ def async(request): def good_rpm_response(r): - return isinstance(r.value, float) and \ - r.value >= 0.0 and \ - r.unit == Unit.RPM + return (r.value.u == Unit.rpm) and (r.value >= 0.0 * Unit.rpm) def test_supports(obd): assert(len(obd.supported_commands) > 0) From edb55baf6bcc6b5fe699f009f43dfaacc650897f Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 29 Jun 2016 17:43:16 -0400 Subject: [PATCH 016/128] added a new unit property for backwards compatibility --- obd/OBDResponse.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/obd/OBDResponse.py b/obd/OBDResponse.py index d9f6c600..073dbb91 100644 --- a/obd/OBDResponse.py +++ b/obd/OBDResponse.py @@ -51,6 +51,16 @@ def __init__(self, command=None, messages=None): self.value = None self.time = time.time() + @property + def unit(self): + # for backwards compatibility + if isinstance(self.value, Unit.Quantity): + return str(self.value.u) + elif self.value == None: + return None + else: + return str(type(self.value)) + def is_null(self): return (not self.messages) or (self.value == None) From 171c0a67247f08428518445b0ecf0643bb8e3463 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 29 Jun 2016 19:51:32 -0400 Subject: [PATCH 017/128] documented pint units --- README.md | 1 - docs/Responses.md | 67 +++++++++++++++++++++++++++-------------------- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 3bc11a5a..c6fe81a8 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,6 @@ cmd = obd.commands.RPM # select an OBD command (sensor) response = connection.query(cmd) # send the command, and parse the response print(response.value) -print(response.unit) ``` Documentation diff --git a/docs/Responses.md b/docs/Responses.md index fe72b3b6..1cb292cd 100644 --- a/docs/Responses.md +++ b/docs/Responses.md @@ -3,12 +3,10 @@ The `query()` function returns `OBDResponse` objects. These objects have the fol | Property | Description | |----------|------------------------------------------------------------------------| | value | The decoded value from the car | -| unit | The units of the decoded value | -| command | The `OBDCommand` object that triggered this response | +| command | The `OBDCommand` object that triggered this response | | message | The internal `Message` object containing the raw response from the car | | time | Timestamp of response (as given by [`time.time()`](https://docs.python.org/2/library/time.html#time.time)) | -The `value` property typically contains numeric values, but can also hold complex structures (depending upon the command that was sent). --- @@ -27,36 +25,47 @@ if not r.is_null(): --- -# Units +# Values -Unit values can be found in the `Unit` class (enum). +The `value` property typically contains a [Pint](http://pint.readthedocs.io/en/latest/) `Quantity` object, but can also hold complex structures (depending on the request). Pint quantities combine a value and unit into a single class, and are used to represent physical values (such as "4 seconds", and "88 mph"). This allows for consistency when doing math and unit conversions. Pint maintains a registry of units, which is exposed in python-OBD as `obd.Unit`. + +Below are common operations that can be done with Pint units and quantities. For more information, check out the [Pint Documentation](http://pint.readthedocs.io/en/latest/). ```python -from obd.utils import Unit -``` +import obd + +>>> response.value + + +# get the raw python datatype +>>> response.value.magnitude +100 + +# converts quantities to strings +>>> str(response.value) +'100 kph' -| Name | Value | -|-------------|--------------------| -| NONE | None | -| RATIO | "Ratio" | -| COUNT | "Count" | -| PERCENT | "%" | -| RPM | "RPM" | -| VOLT | "Volt" | -| F | "F" | -| C | "C" | -| SEC | "Second" | -| MIN | "Minute" | -| PA | "Pa" | -| KPA | "kPa" | -| PSI | "psi" | -| KPH | "kph" | -| MPH | "mph" | -| DEGREES | "Degrees" | -| GPS | "Grams per Second" | -| MA | "mA" | -| KM | "km" | -| LPH | "Liters per Hour" | +# convert strings to quantities +>>> obd.Unit("100 kph") + + +# handles conversions nicely +>>> response.value.to('mph') + + +# scaler math +>>> response.value / 2 + + +# non-scaler math requires you to specify units yourself +>>> response.value + (20 * obd.Unit.kph) + + +# non-scaler math with different units +# handles unit conversions transparently +>>> response.value + (20 * obd.Unit.mph) + +``` --- From 0611a71a4cbe88426f8d15556b3b68aeccdd4585 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 29 Jun 2016 22:15:40 -0400 Subject: [PATCH 018/128] wrote up the mode 06 table with drop decoder --- obd/commands.py | 112 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 109 insertions(+), 3 deletions(-) diff --git a/obd/commands.py b/obd/commands.py index 036e4528..698f4078 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -179,6 +179,108 @@ OBDCommand("CLEAR_DTC" , "Clear DTCs and Freeze data" , b"04", 0, drop, ECU.ALL, False), ] +__mode6__ = [ + # name description cmd bytes decoder ECU fast + OBDCommand("MIDS_A" , "Supported MIDs [01-20]" , b"0600", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_B1S1" , "O2 Sensor Monitor Bank 1 - Sensor 1" , b"0601", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_B1S2" , "O2 Sensor Monitor Bank 1 - Sensor 2" , b"0602", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_B1S3" , "O2 Sensor Monitor Bank 1 - Sensor 3" , b"0603", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_B1S4" , "O2 Sensor Monitor Bank 1 - Sensor 4" , b"0604", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_B2S1" , "O2 Sensor Monitor Bank 2 - Sensor 1" , b"0605", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_B2S2" , "O2 Sensor Monitor Bank 2 - Sensor 2" , b"0606", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_B2S3" , "O2 Sensor Monitor Bank 2 - Sensor 3" , b"0607", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_B2S4" , "O2 Sensor Monitor Bank 2 - Sensor 4" , b"0608", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_B3S1" , "O2 Sensor Monitor Bank 3 - Sensor 1" , b"0609", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_B3S2" , "O2 Sensor Monitor Bank 3 - Sensor 2" , b"060A", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_B3S3" , "O2 Sensor Monitor Bank 3 - Sensor 3" , b"060B", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_B3S4" , "O2 Sensor Monitor Bank 3 - Sensor 4" , b"060C", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_B4S1" , "O2 Sensor Monitor Bank 4 - Sensor 1" , b"060D", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_B4S2" , "O2 Sensor Monitor Bank 4 - Sensor 2" , b"060E", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_B4S3" , "O2 Sensor Monitor Bank 4 - Sensor 3" , b"060F", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_B4S4" , "O2 Sensor Monitor Bank 4 - Sensor 4" , b"0610", 0, drop, ECU.ALL, False), +] + ([None] * 15) + [ # 11 - 1F Reserved + OBDCommand("MIDS_B" , "Supported MIDs [21-40]" , b"0620", 0, drop, ECU.ALL, False), + OBDCommand("MON_CATALYST_B1" , "Catalyst Monitor Bank 1" , b"0621", 0, drop, ECU.ALL, False), + OBDCommand("MON_CATALYST_B2" , "Catalyst Monitor Bank 2" , b"0622", 0, drop, ECU.ALL, False), + OBDCommand("MON_CATALYST_B3" , "Catalyst Monitor Bank 3" , b"0623", 0, drop, ECU.ALL, False), + OBDCommand("MON_CATALYST_B4" , "Catalyst Monitor Bank 4" , b"0624", 0, drop, ECU.ALL, False), +] + ([None] * 12) + [ # 25 - 30 Reserved + OBDCommand("MON_EGR_B1" , "EGR Monitor Bank 1" , b"0631", 0, drop, ECU.ALL, False), + OBDCommand("MON_EGR_B2" , "EGR Monitor Bank 2" , b"0632", 0, drop, ECU.ALL, False), + OBDCommand("MON_EGR_B3" , "EGR Monitor Bank 3" , b"0633", 0, drop, ECU.ALL, False), + OBDCommand("MON_EGR_B4" , "EGR Monitor Bank 4" , b"0634", 0, drop, ECU.ALL, False), + OBDCommand("MON_VVT_B1" , "VVT Monitor Bank 1" , b"0635", 0, drop, ECU.ALL, False), + OBDCommand("MON_VVT_B2" , "VVT Monitor Bank 2" , b"0636", 0, drop, ECU.ALL, False), + OBDCommand("MON_VVT_B3" , "VVT Monitor Bank 3" , b"0637", 0, drop, ECU.ALL, False), + OBDCommand("MON_VVT_B4" , "VVT Monitor Bank 4" , b"0638", 0, drop, ECU.ALL, False), + OBDCommand("MON_EVAP_150" , "EVAP Monitor (Cap Off / 0.150\")" , b"0639", 0, drop, ECU.ALL, False), + OBDCommand("MON_EVAP_090" , "EVAP Monitor (0.090\")" , b"063A", 0, drop, ECU.ALL, False), + OBDCommand("MON_EVAP_040" , "EVAP Monitor (0.040\")" , b"063B", 0, drop, ECU.ALL, False), + OBDCommand("MON_EVAP_020" , "EVAP Monitor (0.020\")" , b"063C", 0, drop, ECU.ALL, False), + OBDCommand("MON_PURGE_FLOW" , "Purge Flow Monitor" , b"063D", 0, drop, ECU.ALL, False), +] + ([None] * 2) + [ # 3E - 3F Reserved + OBDCommand("MIDS_C" , "Supported MIDs [41-60]" , b"0640", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_HEATER_B1S1" , "O2 Sensor Heater Monitor Bank 1 - Sensor 1" , b"0641", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_HEATER_B1S2" , "O2 Sensor Heater Monitor Bank 1 - Sensor 2" , b"0642", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_HEATER_B1S3" , "O2 Sensor Heater Monitor Bank 1 - Sensor 3" , b"0643", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_HEATER_B1S4" , "O2 Sensor Heater Monitor Bank 1 - Sensor 4" , b"0644", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_HEATER_B2S1" , "O2 Sensor Heater Monitor Bank 2 - Sensor 1" , b"0645", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_HEATER_B2S2" , "O2 Sensor Heater Monitor Bank 2 - Sensor 2" , b"0646", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_HEATER_B2S3" , "O2 Sensor Heater Monitor Bank 2 - Sensor 3" , b"0647", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_HEATER_B2S4" , "O2 Sensor Heater Monitor Bank 2 - Sensor 4" , b"0648", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_HEATER_B3S1" , "O2 Sensor Heater Monitor Bank 3 - Sensor 1" , b"0649", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_HEATER_B3S2" , "O2 Sensor Heater Monitor Bank 3 - Sensor 2" , b"064A", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_HEATER_B3S3" , "O2 Sensor Heater Monitor Bank 3 - Sensor 3" , b"064B", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_HEATER_B3S4" , "O2 Sensor Heater Monitor Bank 3 - Sensor 4" , b"064C", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_HEATER_B4S1" , "O2 Sensor Heater Monitor Bank 4 - Sensor 1" , b"064D", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_HEATER_B4S2" , "O2 Sensor Heater Monitor Bank 4 - Sensor 2" , b"064E", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_HEATER_B4S3" , "O2 Sensor Heater Monitor Bank 4 - Sensor 3" , b"064F", 0, drop, ECU.ALL, False), + OBDCommand("MON_O2_HEATER_B4S4" , "O2 Sensor Heater Monitor Bank 4 - Sensor 4" , b"0650", 0, drop, ECU.ALL, False), +] + ([None] * 15) + [ # 51 - 5F Reserved + OBDCommand("MIDS_D" , "Supported MIDs [61-80]" , b"0660", 0, drop, ECU.ALL, False), + OBDCommand("MON_HEATED_CATALYST_B1" , "Heated Catalyst Monitor Bank 1" , b"0661", 0, drop, ECU.ALL, False), + OBDCommand("MON_HEATED_CATALYST_B2" , "Heated Catalyst Monitor Bank 2" , b"0662", 0, drop, ECU.ALL, False), + OBDCommand("MON_HEATED_CATALYST_B3" , "Heated Catalyst Monitor Bank 3" , b"0663", 0, drop, ECU.ALL, False), + OBDCommand("MON_HEATED_CATALYST_B4" , "Heated Catalyst Monitor Bank 4" , b"0664", 0, drop, ECU.ALL, False), +] + ([None] * 12) + [ # 65 - 70 Reserved + OBDCommand("MON_SECONDARY_AIR_1" , "Secondary Air Monitor 1" , b"0671", 0, drop, ECU.ALL, False), + OBDCommand("MON_SECONDARY_AIR_2" , "Secondary Air Monitor 2" , b"0672", 0, drop, ECU.ALL, False), + OBDCommand("MON_SECONDARY_AIR_3" , "Secondary Air Monitor 3" , b"0673", 0, drop, ECU.ALL, False), + OBDCommand("MON_SECONDARY_AIR_4" , "Secondary Air Monitor 4" , b"0674", 0, drop, ECU.ALL, False), +] + ([None] * 11) + [ # 75 - 7F Reserved + OBDCommand("MIDS_E" , "Supported MIDs [81-A0]" , b"0680", 0, drop, ECU.ALL, False), + OBDCommand("MON_FUEL_SYSTEM_B1" , "Fuel System Monitor Bank 1" , b"0681", 0, drop, ECU.ALL, False), + OBDCommand("MON_FUEL_SYSTEM_B2" , "Fuel System Monitor Bank 2" , b"0682", 0, drop, ECU.ALL, False), + OBDCommand("MON_FUEL_SYSTEM_B3" , "Fuel System Monitor Bank 3" , b"0683", 0, drop, ECU.ALL, False), + OBDCommand("MON_FUEL_SYSTEM_B4" , "Fuel System Monitor Bank 4" , b"0684", 0, drop, ECU.ALL, False), + OBDCommand("MON_BOOST_PRESSURE_B1" , "Boost Pressure Control Monitor Bank 1" , b"0685", 0, drop, ECU.ALL, False), + OBDCommand("MON_BOOST_PRESSURE_B2" , "Boost Pressure Control Monitor Bank 1" , b"0686", 0, drop, ECU.ALL, False), +] + ([None] * 9) + [ # 87 - 8F Reserved + OBDCommand("MON_NOX_ABSORBER_B1" , "NOx Absorber Monitor Bank 1" , b"0690", 0, drop, ECU.ALL, False), + OBDCommand("MON_NOX_ABSORBER_B2" , "NOx Absorber Monitor Bank 2" , b"0691", 0, drop, ECU.ALL, False), +] + ([None] * 6) + [ # 92 - 97 Reserved + OBDCommand("MON_NOX_CATALYST_B1" , "NOx Catalyst Monitor Bank 1" , b"0698", 0, drop, ECU.ALL, False), + OBDCommand("MON_NOX_CATALYST_B2" , "NOx Catalyst Monitor Bank 2" , b"0699", 0, drop, ECU.ALL, False), +] + ([None] * 6) + [ # 9A - 9F Reserved + OBDCommand("MIDS_F" , "Supported MIDs [A1-C0]" , b"06A0", 0, drop, ECU.ALL, False), + OBDCommand("MON_MISFIRE_GENERAL" , "Misfire Monitor General Data" , b"06A1", 0, drop, ECU.ALL, False), + OBDCommand("MON_MISFIRE_CYLINDER_1" , "Misfire Cylinder 1 Data" , b"06A2", 0, drop, ECU.ALL, False), + OBDCommand("MON_MISFIRE_CYLINDER_2" , "Misfire Cylinder 2 Data" , b"06A3", 0, drop, ECU.ALL, False), + OBDCommand("MON_MISFIRE_CYLINDER_3" , "Misfire Cylinder 3 Data" , b"06A4", 0, drop, ECU.ALL, False), + OBDCommand("MON_MISFIRE_CYLINDER_4" , "Misfire Cylinder 4 Data" , b"06A5", 0, drop, ECU.ALL, False), + OBDCommand("MON_MISFIRE_CYLINDER_5" , "Misfire Cylinder 5 Data" , b"06A6", 0, drop, ECU.ALL, False), + OBDCommand("MON_MISFIRE_CYLINDER_6" , "Misfire Cylinder 6 Data" , b"06A7", 0, drop, ECU.ALL, False), + OBDCommand("MON_MISFIRE_CYLINDER_7" , "Misfire Cylinder 7 Data" , b"06A8", 0, drop, ECU.ALL, False), + OBDCommand("MON_MISFIRE_CYLINDER_8" , "Misfire Cylinder 8 Data" , b"06A9", 0, drop, ECU.ALL, False), + OBDCommand("MON_MISFIRE_CYLINDER_9" , "Misfire Cylinder 9 Data" , b"06AA", 0, drop, ECU.ALL, False), + OBDCommand("MON_MISFIRE_CYLINDER_10" , "Misfire Cylinder 10 Data" , b"06AB", 0, drop, ECU.ALL, False), + OBDCommand("MON_MISFIRE_CYLINDER_11" , "Misfire Cylinder 11 Data" , b"06AC", 0, drop, ECU.ALL, False), + OBDCommand("MON_MISFIRE_CYLINDER_12" , "Misfire Cylinder 12 Data" , b"06AD", 0, drop, ECU.ALL, False), +] + ([None] * 2) + [ # AE - AF Reserved + OBDCommand("MON_PM_FILTER_B1" , "PM Filter Monitor Bank 1" , b"06B0", 0, drop, ECU.ALL, False), + OBDCommand("MON_PM_FILTER_B2" , "PM Filter Monitor Bank 2" , b"06B1", 0, drop, ECU.ALL, False), +] + __mode7__ = [ # name description cmd bytes decoder ECU fast OBDCommand("GET_FREEZE_DTC" , "Get Freeze DTCs" , b"07", 0, dtc, ECU.ALL, False), @@ -214,7 +316,7 @@ def __init__(self): __mode3__, __mode4__, [], - [], + __mode6__, __mode7__, [], __mode9__, @@ -223,7 +325,8 @@ def __init__(self): # allow commands to be accessed by name for m in self.modes: for c in m: - self.__dict__[c.name] = c + if c is not None: + self.__dict__[c.name] = c for c in __misc__: self.__dict__[c.name] = c @@ -320,7 +423,10 @@ def has_pid(self, mode, pid): return False if pid >= len(self.modes[mode]): return False - return True + + # make sure that the command isn't reserved + return (self.modes[mode][pid] is not None) + else: logger.warning("has_pid() only accepts integer values for mode and PID") return False From 8d167930170e4aa6eed63cfaf5b0c4c5a00d0bfd Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 29 Jun 2016 22:54:09 -0400 Subject: [PATCH 019/128] use pid decoder for MID getters --- obd/commands.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/obd/commands.py b/obd/commands.py index 698f4078..db5f6c2e 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -180,8 +180,10 @@ ] __mode6__ = [ + # Mode 06 calls PID's MID's (Monitor ID) + # This is for CAN only # name description cmd bytes decoder ECU fast - OBDCommand("MIDS_A" , "Supported MIDs [01-20]" , b"0600", 0, drop, ECU.ALL, False), + OBDCommand("MIDS_A" , "Supported MIDs [01-20]" , b"0600", 0, pid, ECU.ALL, False), OBDCommand("MON_O2_B1S1" , "O2 Sensor Monitor Bank 1 - Sensor 1" , b"0601", 0, drop, ECU.ALL, False), OBDCommand("MON_O2_B1S2" , "O2 Sensor Monitor Bank 1 - Sensor 2" , b"0602", 0, drop, ECU.ALL, False), OBDCommand("MON_O2_B1S3" , "O2 Sensor Monitor Bank 1 - Sensor 3" , b"0603", 0, drop, ECU.ALL, False), @@ -199,7 +201,7 @@ OBDCommand("MON_O2_B4S3" , "O2 Sensor Monitor Bank 4 - Sensor 3" , b"060F", 0, drop, ECU.ALL, False), OBDCommand("MON_O2_B4S4" , "O2 Sensor Monitor Bank 4 - Sensor 4" , b"0610", 0, drop, ECU.ALL, False), ] + ([None] * 15) + [ # 11 - 1F Reserved - OBDCommand("MIDS_B" , "Supported MIDs [21-40]" , b"0620", 0, drop, ECU.ALL, False), + OBDCommand("MIDS_B" , "Supported MIDs [21-40]" , b"0620", 0, pid, ECU.ALL, False), OBDCommand("MON_CATALYST_B1" , "Catalyst Monitor Bank 1" , b"0621", 0, drop, ECU.ALL, False), OBDCommand("MON_CATALYST_B2" , "Catalyst Monitor Bank 2" , b"0622", 0, drop, ECU.ALL, False), OBDCommand("MON_CATALYST_B3" , "Catalyst Monitor Bank 3" , b"0623", 0, drop, ECU.ALL, False), @@ -219,7 +221,7 @@ OBDCommand("MON_EVAP_020" , "EVAP Monitor (0.020\")" , b"063C", 0, drop, ECU.ALL, False), OBDCommand("MON_PURGE_FLOW" , "Purge Flow Monitor" , b"063D", 0, drop, ECU.ALL, False), ] + ([None] * 2) + [ # 3E - 3F Reserved - OBDCommand("MIDS_C" , "Supported MIDs [41-60]" , b"0640", 0, drop, ECU.ALL, False), + OBDCommand("MIDS_C" , "Supported MIDs [41-60]" , b"0640", 0, pid, ECU.ALL, False), OBDCommand("MON_O2_HEATER_B1S1" , "O2 Sensor Heater Monitor Bank 1 - Sensor 1" , b"0641", 0, drop, ECU.ALL, False), OBDCommand("MON_O2_HEATER_B1S2" , "O2 Sensor Heater Monitor Bank 1 - Sensor 2" , b"0642", 0, drop, ECU.ALL, False), OBDCommand("MON_O2_HEATER_B1S3" , "O2 Sensor Heater Monitor Bank 1 - Sensor 3" , b"0643", 0, drop, ECU.ALL, False), @@ -237,7 +239,7 @@ OBDCommand("MON_O2_HEATER_B4S3" , "O2 Sensor Heater Monitor Bank 4 - Sensor 3" , b"064F", 0, drop, ECU.ALL, False), OBDCommand("MON_O2_HEATER_B4S4" , "O2 Sensor Heater Monitor Bank 4 - Sensor 4" , b"0650", 0, drop, ECU.ALL, False), ] + ([None] * 15) + [ # 51 - 5F Reserved - OBDCommand("MIDS_D" , "Supported MIDs [61-80]" , b"0660", 0, drop, ECU.ALL, False), + OBDCommand("MIDS_D" , "Supported MIDs [61-80]" , b"0660", 0, pid, ECU.ALL, False), OBDCommand("MON_HEATED_CATALYST_B1" , "Heated Catalyst Monitor Bank 1" , b"0661", 0, drop, ECU.ALL, False), OBDCommand("MON_HEATED_CATALYST_B2" , "Heated Catalyst Monitor Bank 2" , b"0662", 0, drop, ECU.ALL, False), OBDCommand("MON_HEATED_CATALYST_B3" , "Heated Catalyst Monitor Bank 3" , b"0663", 0, drop, ECU.ALL, False), @@ -248,7 +250,7 @@ OBDCommand("MON_SECONDARY_AIR_3" , "Secondary Air Monitor 3" , b"0673", 0, drop, ECU.ALL, False), OBDCommand("MON_SECONDARY_AIR_4" , "Secondary Air Monitor 4" , b"0674", 0, drop, ECU.ALL, False), ] + ([None] * 11) + [ # 75 - 7F Reserved - OBDCommand("MIDS_E" , "Supported MIDs [81-A0]" , b"0680", 0, drop, ECU.ALL, False), + OBDCommand("MIDS_E" , "Supported MIDs [81-A0]" , b"0680", 0, pid, ECU.ALL, False), OBDCommand("MON_FUEL_SYSTEM_B1" , "Fuel System Monitor Bank 1" , b"0681", 0, drop, ECU.ALL, False), OBDCommand("MON_FUEL_SYSTEM_B2" , "Fuel System Monitor Bank 2" , b"0682", 0, drop, ECU.ALL, False), OBDCommand("MON_FUEL_SYSTEM_B3" , "Fuel System Monitor Bank 3" , b"0683", 0, drop, ECU.ALL, False), @@ -262,7 +264,7 @@ OBDCommand("MON_NOX_CATALYST_B1" , "NOx Catalyst Monitor Bank 1" , b"0698", 0, drop, ECU.ALL, False), OBDCommand("MON_NOX_CATALYST_B2" , "NOx Catalyst Monitor Bank 2" , b"0699", 0, drop, ECU.ALL, False), ] + ([None] * 6) + [ # 9A - 9F Reserved - OBDCommand("MIDS_F" , "Supported MIDs [A1-C0]" , b"06A0", 0, drop, ECU.ALL, False), + OBDCommand("MIDS_F" , "Supported MIDs [A1-C0]" , b"06A0", 0, pid, ECU.ALL, False), OBDCommand("MON_MISFIRE_GENERAL" , "Misfire Monitor General Data" , b"06A1", 0, drop, ECU.ALL, False), OBDCommand("MON_MISFIRE_CYLINDER_1" , "Misfire Cylinder 1 Data" , b"06A2", 0, drop, ECU.ALL, False), OBDCommand("MON_MISFIRE_CYLINDER_2" , "Misfire Cylinder 2 Data" , b"06A3", 0, drop, ECU.ALL, False), From 2b58457b859875868a4e1a304ef08bd50ab9aaa4 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 29 Jun 2016 23:05:57 -0400 Subject: [PATCH 020/128] fixed tests for None reserved commands, and mode_int -> mode --- obd/OBDCommand.py | 4 ++-- obd/commands.py | 13 +++++++++---- obd/obd.py | 4 ++-- tests/test_OBDCommand.py | 16 ++++++++-------- tests/test_commands.py | 33 +++++++++++++++++++++++++-------- 5 files changed, 46 insertions(+), 24 deletions(-) diff --git a/obd/OBDCommand.py b/obd/OBDCommand.py index 0fc023ab..c007816a 100644 --- a/obd/OBDCommand.py +++ b/obd/OBDCommand.py @@ -65,14 +65,14 @@ def clone(self): self.fast) @property - def mode_int(self): + def mode(self): if len(self.command) >= 2: return unhex(self.command[:2]) else: return 0 @property - def pid_int(self): + def pid(self): if len(self.command) > 2: return unhex(self.command[2:]) else: diff --git a/obd/commands.py b/obd/commands.py index db5f6c2e..04739de0 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -382,10 +382,15 @@ def base_commands(self): def pid_getters(self): """ returns a list of PID GET commands """ getters = [] - for m in self.modes: - for c in m: - if c.decode == pid: # GET commands have a special decoder - getters.append(c) + for mode in self.modes: + for cmd in mode: + + if cmd is None: + continue # this command is reserved + + if cmd.decode == pid: # GET commands have a special decoder + getters.append(cmd) + return getters diff --git a/obd/obd.py b/obd/obd.py index a3004f73..8efbb52b 100644 --- a/obd/obd.py +++ b/obd/obd.py @@ -120,8 +120,8 @@ def __load_commands(self): for i in range(len(supported)): if supported[i] == "1": - mode = get.mode_int - pid = get.pid_int + i + 1 + mode = get.mode + pid = get.pid + i + 1 if commands.has_pid(mode, pid): self.supported_commands.add(commands[mode][pid]) diff --git a/tests/test_OBDCommand.py b/tests/test_OBDCommand.py index b0bb4aa7..bad9b8b8 100644 --- a/tests/test_OBDCommand.py +++ b/tests/test_OBDCommand.py @@ -19,8 +19,8 @@ def test_constructor(): assert cmd.ecu == ECU.ENGINE assert cmd.fast == False - assert cmd.mode_int == 1 - assert cmd.pid_int == 35 + assert cmd.mode == 1 + assert cmd.pid == 35 # a case where "fast", and "supported" were set explicitly # name description cmd bytes decoder ECU fast @@ -67,18 +67,18 @@ def test_call(): -def test_get_mode_int(): +def test_get_mode(): cmd = OBDCommand("", "", "0123", 4, noop, ECU.ENGINE) - assert cmd.mode_int == 0x01 + assert cmd.mode == 0x01 cmd = OBDCommand("", "", "", "23", 4, noop, ECU.ENGINE) - assert cmd.mode_int == 0 + assert cmd.mode == 0 -def test_pid_int(): +def test_pid(): cmd = OBDCommand("", "", "0123", 4, noop, ECU.ENGINE) - assert cmd.pid_int == 0x23 + assert cmd.pid == 0x23 cmd = OBDCommand("", "", "01", 4, noop, ECU.ENGINE) - assert cmd.pid_int == 0 + assert cmd.pid == 0 diff --git a/tests/test_commands.py b/tests/test_commands.py index 0ac0df2f..dac06ade 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -7,11 +7,14 @@ def test_list_integrity(): for mode, cmds in enumerate(obd.commands.modes): for pid, cmd in enumerate(cmds): + if cmd is None: + continue # this command is reserved + assert cmd.command != "", "The Command's command string must not be null" # make sure the command tables are in mode & PID order - assert mode == cmd.mode_int, "Command is in the wrong mode list: %s" % cmd.name - assert pid == cmd.pid_int, "The index in the list must also be the PID: %s" % cmd.name + assert mode == cmd.mode, "Command is in the wrong mode list: %s" % cmd.name + assert pid == cmd.pid, "The index in the list must also be the PID: %s" % cmd.name # make sure all the fields are set assert cmd.name != "", "Command names must not be null" @@ -30,6 +33,10 @@ def test_unique_names(): for cmds in obd.commands.modes: for cmd in cmds: + + if cmd is None: + continue # this command is reserved + assert not names.__contains__(cmd.name), "Two commands share the same name: %s" % cmd.name names[cmd.name] = True @@ -39,9 +46,12 @@ def test_getitem(): for cmds in obd.commands.modes: for cmd in cmds: + if cmd is None: + continue # this command is reserved + # by [mode][pid] - mode = cmd.mode_int - pid = cmd.pid_int + mode = cmd.mode + pid = cmd.pid assert cmd == obd.commands[mode][pid], "mode %d, PID %d could not be accessed through __getitem__" % (mode, pid) # by [name] @@ -53,12 +63,15 @@ def test_contains(): for cmds in obd.commands.modes: for cmd in cmds: + if cmd is None: + continue # this command is reserved + # by (command) assert obd.commands.has_command(cmd) # by (mode, pid) - mode = cmd.mode_int - pid = cmd.pid_int + mode = cmd.mode + pid = cmd.pid assert obd.commands.has_pid(mode, pid) # by (name) @@ -78,7 +91,11 @@ def test_pid_getters(): # ensure that all pid getters are found pid_getters = obd.commands.pid_getters() - for cmds in obd.commands.modes: - for cmd in cmds: + for mode in obd.commands.modes: + for cmd in mode: + + if cmd is None: + continue # this command is reserved + if cmd.decode == pid: assert cmd in pid_getters From a6440158e2ab68da793790628f5d18dc57929985 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 29 Jun 2016 23:18:14 -0400 Subject: [PATCH 021/128] added TID table --- obd/codes.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/obd/codes.py b/obd/codes.py index 8278d922..ca394277 100644 --- a/obd/codes.py +++ b/obd/codes.py @@ -2205,3 +2205,20 @@ "Hybrid Regenerative", "Bifuel running diesel", ] + +TEST_IDS = { + # : + # 0x0 is reserved + 0x1 : ("rtl_threshold voltage", "Rich to lean sensor threshold voltage"), + 0x2 : ("ltr_threshold voltage", "Lean to rich sensor threshold voltage"), + 0x3 : ("low_voltage_switch_time", "Low sensor voltage for switch time calculation"), + 0x4 : ("high_voltage_switch_time", "High sensor voltage for switch time calculation"), + 0x5 : ("rtl_switch_time", "Rich to lean sensor switch time"), + 0x6 : ("ltr_switch_time", "Lean to rich sensor switch time"), + 0x7 : ("min_voltage", "Minimum sensor voltage for test cycle"), + 0x8 : ("max_voltage", "Maximum sensor voltage for test cycle"), + 0x9 : ("transition_time", "Time between sensor transitions"), + 0xA : ("sensor_period", "Sensor period"), + 0xB : ("misfire_average", "Average misfire counts for last ten driving cycles"), + 0xC : ("misfire_count", "Misfire counts for last/current driving cycles"), +} From 1abddb87eb4b5e662663a0ec2219e2bb9dd02af1 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 30 Jun 2016 01:24:21 -0400 Subject: [PATCH 022/128] started implementing monitor decoders and UAS system --- obd/OBDResponse.py | 18 +++++++++++++++ obd/decoders.py | 32 ++++++++++++++++++++++++++- obd/uas.py | 55 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 obd/uas.py diff --git a/obd/OBDResponse.py b/obd/OBDResponse.py index 073dbb91..ef09b0ba 100644 --- a/obd/OBDResponse.py +++ b/obd/OBDResponse.py @@ -33,6 +33,7 @@ import time import pint +from .codes import * # export the unit registry @@ -93,3 +94,20 @@ def __str__(self): a = "Available" if self.available else "Unavailable" c = "Incomplete" if self.incomplete else "Complete" return "Test %s: %s, %s" % (self.name, a, c) + + +class Monitor(): + def __init__(self): + # make all TID tests available as properties + for tid in TEST_IDS: + self.__dict__[TEST_IDS[tid][0]] = MonitorTest() + + +class MonitorTest(): + def __init__(self): + self.tid = None + self.name = None + self.desc = None + self.value = None + self.min = None + self.max = None diff --git a/obd/decoders.py b/obd/decoders.py index 7f8baa2e..8c8c72d0 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -32,7 +32,7 @@ import math from .utils import * from .codes import * -from .OBDResponse import Unit, Status, Test +from .OBDResponse import Unit, Status, Test, Monitor, MonitorTest import logging @@ -425,3 +425,33 @@ def dtc(messages): codes.append( (dtc, desc) ) return codes + + +def monitor_test(d): + test = MonitorTest() + test.tid = bytes_to_int(test_data[1]) + test.name = TEST_IDS[test.tid][0] # lookup the description from the table + test.desc = TEST_IDS[test.tid][1] # lookup the description from the table + + + + return test + + +def monitor(messages): + d = messages[0].data + mon = Monitor() + + # test that we got the right number of bytes + extra_bytes = len(d) % 9 + + if extra_bytes != 0: + logger.debug("Encountered monitor message with non-multiple of 9 bytes. Truncating...") + d = d[:len(d) - extra_bytes] + + # look at data in blocks of 9 bytes (one test result) + for n in range(0, len(d), 9): + test = monitor_test(d[n:n + 8]) # extract the 9 byte block, and parse a new MonitorTest + setattr(mon,test.name, test) # use the "name" field as the property + + return mon diff --git a/obd/uas.py b/obd/uas.py new file mode 100644 index 00000000..78655f37 --- /dev/null +++ b/obd/uas.py @@ -0,0 +1,55 @@ + +######################################################################## +# # +# python-OBD: A python OBD-II serial module derived from pyobd # +# # +# Copyright 2004 Donour Sizemore (donour@uchicago.edu) # +# Copyright 2009 Secons Ltd. (www.obdtester.com) # +# Copyright 2009 Peter J. Creath # +# Copyright 2016 Brendan Whitfield (brendan-w.com) # +# # +######################################################################## +# # +# uac.py # +# # +# This file is part of python-OBD (a derivative of pyOBD) # +# # +# python-OBD is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 2 of the License, or # +# (at your option) any later version. # +# # +# python-OBD is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with python-OBD. If not, see . # +# # +######################################################################## + +from .utils import * +from .OBDResponse import Unit + + +class UAS(): + """ + Class for representing a Unit and Scale conversion + Used in the decoding of Mode 06 monitor responses + """ + + def __init__(self, signed, scale, unit): + self.signed = signed + self.scale = scale + self.unit = unit + + def __call__(self, _bytes): + value = bytes_to_int(_bytes) + + if self.signed: + value = twos_comp(value, len(_bytes) * 8) + + value *= self.scale + + return Unit.Quantity(value, self.unit) From 0a227c0da50332ecd055d93bc709b6148a94e743 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 30 Jun 2016 01:51:00 -0400 Subject: [PATCH 023/128] started filling in the UAS ID table --- obd/OBDResponse.py | 1 + obd/uas.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/obd/OBDResponse.py b/obd/OBDResponse.py index ef09b0ba..2417bae3 100644 --- a/obd/OBDResponse.py +++ b/obd/OBDResponse.py @@ -39,6 +39,7 @@ # export the unit registry Unit = pint.UnitRegistry() Unit.define("percent = [] = %") +Unit.define("ratio = []") Unit.define("gps = gram / second = GPS = grams_per_second") Unit.define("lph = liter / hour = LPH = liters_per_hour") diff --git a/obd/uas.py b/obd/uas.py index 78655f37..42ae9883 100644 --- a/obd/uas.py +++ b/obd/uas.py @@ -53,3 +53,48 @@ def __call__(self, _bytes): value *= self.scale return Unit.Quantity(value, self.unit) + + +# dict for looking up standardized UAS IDs with conversion objects +UAS_IDS = { + 0x01 : UAS(False, 1, Unit.count), + 0x02 : UAS(False, 0.1, Unit.count), + 0x03 : UAS(False, 0.01, Unit.count), + 0x04 : UAS(False, 0.001, Unit.count), + 0x05 : UAS(False, 0.0000305, Unit.count), + 0x06 : UAS(False, 0.000305, Unit.count), + 0x07 : UAS(False, 0.25, Unit.rpm), + 0x08 : UAS(False, 0.01, Unit.kph), + 0x09 : UAS(False, 1, Unit.kph), + 0x0A : UAS(False, 0.122, Unit.millivolt), + 0x0B : UAS(False, 0.001, Unit.volt), + 0x0C : UAS(False, 0.01, Unit.volt), + 0x0D : UAS(False, 0.00390625, Unit.milliampere), + 0x0E : UAS(False, 0.001, Unit.ampere), + 0x0F : UAS(False, 0.01, Unit.ampere), + 0x10 : UAS(False, 1, Unit.millisecond), + 0x11 : UAS(False, 100, Unit.millisecond), + 0x12 : UAS(False, 1, Unit.second), + 0x13 : UAS(False, 1, Unit.milliohm), + 0x14 : UAS(False, 1, Unit.ohm), + 0x15 : UAS(False, 1, Unit.kiloohm), + 0x16 : None, # TODO + 0x17 : UAS(False, 0.01, Unit.kilopascal), + 0x18 : UAS(False, 0.0117, Unit.kilopascal), + 0x19 : UAS(False, 0.079, Unit.kilopascal), + 0x1A : UAS(False, 1, Unit.kilopascal), + 0x1B : UAS(False, 10, Unit.kilopascal), + 0x1C : UAS(False, 0.01, Unit.degree), + 0x1D : UAS(False, 0.5, Unit.degree), + 0x1E : None, # TODO + 0x1F : UAS(False, 0.05, Unit.ratio), + 0x20 : UAS(False, 0.00390625, Unit.ratio), + 0x21 : UAS(False, 1, Unit.millihertz), + 0x22 : UAS(False, 1, Unit.hertz), + 0x23 : UAS(False, 1, Unit.kilohertz), + 0x24 : UAS(False, 1, Unit.count), + 0x25 : UAS(False, 1, Unit.kilometer), + 0x26 : UAS(False, 0.1, Unit.millivolt / Unit.millisecond), + 0x27 : UAS(False, 0.1, Unit.grams_per_second), + 0x28 : UAS(False, 1, Unit.grams_per_second), +} From 0d3dcfb1678d9d5e531cc43c68aed43b133fa312 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 30 Jun 2016 12:31:11 -0400 Subject: [PATCH 024/128] special handling for mode 6 in CAN protocol --- obd/protocols/protocol_can.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/obd/protocols/protocol_can.py b/obd/protocols/protocol_can.py index ab1f34f7..8d4b2191 100644 --- a/obd/protocols/protocol_can.py +++ b/obd/protocols/protocol_can.py @@ -264,6 +264,8 @@ def parse_message(self, message): message.data = message.data[:ff[0].data_len] + # TODO: this is an ugly solution, maybe move mode/pid byte ignoring to the decoders? + # chop off the Mode/PID bytes based on the mode number mode = message.data[0] if mode == 0x43: @@ -278,6 +280,12 @@ def parse_message(self, message): # skip the PID byte and the DTC count, message.data = message.data[2:][:num_dtc_bytes] + elif mode == 0x46: + # the monitor test mode only has a mode number + # the MID (mode 6's version of a PID) is repeated, + # and handled in the decoder + message.data = message.data[1:] + else: # skip the Mode and PID bytes # From e78045ea041e58607c27dcf5c40a04436b9b4fe1 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 30 Jun 2016 12:47:31 -0400 Subject: [PATCH 025/128] implemented __str__ for monitors --- obd/OBDResponse.py | 27 +++++++++++++++++++++++++-- obd/codes.py | 24 ++++++++++++------------ obd/decoders.py | 1 - 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/obd/OBDResponse.py b/obd/OBDResponse.py index 2417bae3..d154b2bb 100644 --- a/obd/OBDResponse.py +++ b/obd/OBDResponse.py @@ -101,14 +101,37 @@ class Monitor(): def __init__(self): # make all TID tests available as properties for tid in TEST_IDS: - self.__dict__[TEST_IDS[tid][0]] = MonitorTest() + name = TEST_IDS[tid][0] + self.__dict__[name] = MonitorTest() + + def __str__(self): + output = "" + + for tid in TEST_IDS: + name = TEST_IDS[tid][0] + test = self.__dict__[name] + if not test.is_null(): + output += str(test) + "\n" + + return output class MonitorTest(): def __init__(self): self.tid = None - self.name = None self.desc = None self.value = None self.min = None self.max = None + + @property + def passed(self): + return (self.value >= self.min) and (self.value <= self.max) + + def is_null(self): + return self.tid is None or self.value is None + + def __str__(self): + return "%s : %s [%s]" % (self.desc, + str(self.value), + "PASSED" if self.passed else "FAILED") diff --git a/obd/codes.py b/obd/codes.py index ca394277..c453a139 100644 --- a/obd/codes.py +++ b/obd/codes.py @@ -2209,16 +2209,16 @@ TEST_IDS = { # : # 0x0 is reserved - 0x1 : ("rtl_threshold voltage", "Rich to lean sensor threshold voltage"), - 0x2 : ("ltr_threshold voltage", "Lean to rich sensor threshold voltage"), - 0x3 : ("low_voltage_switch_time", "Low sensor voltage for switch time calculation"), - 0x4 : ("high_voltage_switch_time", "High sensor voltage for switch time calculation"), - 0x5 : ("rtl_switch_time", "Rich to lean sensor switch time"), - 0x6 : ("ltr_switch_time", "Lean to rich sensor switch time"), - 0x7 : ("min_voltage", "Minimum sensor voltage for test cycle"), - 0x8 : ("max_voltage", "Maximum sensor voltage for test cycle"), - 0x9 : ("transition_time", "Time between sensor transitions"), - 0xA : ("sensor_period", "Sensor period"), - 0xB : ("misfire_average", "Average misfire counts for last ten driving cycles"), - 0xC : ("misfire_count", "Misfire counts for last/current driving cycles"), + 0x01 : ("rtl_threshold voltage", "Rich to lean sensor threshold voltage"), + 0x02 : ("ltr_threshold voltage", "Lean to rich sensor threshold voltage"), + 0x03 : ("low_voltage_switch_time", "Low sensor voltage for switch time calculation"), + 0x04 : ("high_voltage_switch_time", "High sensor voltage for switch time calculation"), + 0x05 : ("rtl_switch_time", "Rich to lean sensor switch time"), + 0x06 : ("ltr_switch_time", "Lean to rich sensor switch time"), + 0x07 : ("min_voltage", "Minimum sensor voltage for test cycle"), + 0x08 : ("max_voltage", "Maximum sensor voltage for test cycle"), + 0x09 : ("transition_time", "Time between sensor transitions"), + 0x0A : ("sensor_period", "Sensor period"), + 0x0B : ("misfire_average", "Average misfire counts for last ten driving cycles"), + 0x0C : ("misfire_count", "Misfire counts for last/current driving cycles"), } diff --git a/obd/decoders.py b/obd/decoders.py index 8c8c72d0..6f4b6601 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -430,7 +430,6 @@ def dtc(messages): def monitor_test(d): test = MonitorTest() test.tid = bytes_to_int(test_data[1]) - test.name = TEST_IDS[test.tid][0] # lookup the description from the table test.desc = TEST_IDS[test.tid][1] # lookup the description from the table From c492272752dc01f689a5d04908f08af55ef5260e Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 30 Jun 2016 12:52:33 -0400 Subject: [PATCH 026/128] simplified __str__ code --- obd/OBDResponse.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/obd/OBDResponse.py b/obd/OBDResponse.py index d154b2bb..ba9cc5d4 100644 --- a/obd/OBDResponse.py +++ b/obd/OBDResponse.py @@ -99,21 +99,18 @@ def __str__(self): class Monitor(): def __init__(self): + self.tests = [] + # make all TID tests available as properties for tid in TEST_IDS: name = TEST_IDS[tid][0] - self.__dict__[name] = MonitorTest() + test = MonitorTest() + self.__dict__[name] = test + self.tests.append(test) def __str__(self): - output = "" - - for tid in TEST_IDS: - name = TEST_IDS[tid][0] - test = self.__dict__[name] - if not test.is_null(): - output += str(test) + "\n" - - return output + valid_tests = [str(test) for test in tests if not test.is_null()] + return "\n".join(valid_tests) class MonitorTest(): From 4a37c7b6ff24f966b479c339d795c6386161a836 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 30 Jun 2016 12:58:16 -0400 Subject: [PATCH 027/128] moved UnitRegistry to the UAS file --- obd/OBDResponse.py | 10 ---------- obd/{uas.py => UnitsAndScaling.py} | 13 +++++++++++-- obd/__init__.py | 3 ++- obd/decoders.py | 5 ++--- 4 files changed, 15 insertions(+), 16 deletions(-) rename obd/{uas.py => UnitsAndScaling.py} (93%) diff --git a/obd/OBDResponse.py b/obd/OBDResponse.py index ba9cc5d4..b5dbfea5 100644 --- a/obd/OBDResponse.py +++ b/obd/OBDResponse.py @@ -30,20 +30,10 @@ ######################################################################## - import time -import pint from .codes import * -# export the unit registry -Unit = pint.UnitRegistry() -Unit.define("percent = [] = %") -Unit.define("ratio = []") -Unit.define("gps = gram / second = GPS = grams_per_second") -Unit.define("lph = liter / hour = LPH = liters_per_hour") - - class OBDResponse(): """ Standard response object for any OBDCommand """ diff --git a/obd/uas.py b/obd/UnitsAndScaling.py similarity index 93% rename from obd/uas.py rename to obd/UnitsAndScaling.py index 42ae9883..3c2e8875 100644 --- a/obd/uas.py +++ b/obd/UnitsAndScaling.py @@ -10,7 +10,7 @@ # # ######################################################################## # # -# uac.py # +# UnitsAndScaling.py # # # # This file is part of python-OBD (a derivative of pyOBD) # # # @@ -29,8 +29,17 @@ # # ######################################################################## +import pint from .utils import * -from .OBDResponse import Unit + + +# export the unit registry +Unit = pint.UnitRegistry() +Unit.define("percent = [] = %") +Unit.define("ratio = []") +Unit.define("gps = gram / second = GPS = grams_per_second") +Unit.define("lph = liter / hour = LPH = liters_per_hour") + class UAS(): diff --git a/obd/__init__.py b/obd/__init__.py index 1e7851f4..6781af6c 100644 --- a/obd/__init__.py +++ b/obd/__init__.py @@ -41,9 +41,10 @@ from .async import Async from .commands import commands from .OBDCommand import OBDCommand -from .OBDResponse import OBDResponse, Unit +from .OBDResponse import OBDResponse from .protocols import ECU from .utils import scan_serial, scanSerial, OBDStatus # TODO: scanSerial() deprecated +from .UnitsAndScaling import Unit import logging diff --git a/obd/decoders.py b/obd/decoders.py index 6f4b6601..319a53d9 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -32,7 +32,8 @@ import math from .utils import * from .codes import * -from .OBDResponse import Unit, Status, Test, Monitor, MonitorTest +from .OBDResponse import Status, Test, Monitor, MonitorTest +from .UnitsAndScaling import Unit import logging @@ -432,8 +433,6 @@ def monitor_test(d): test.tid = bytes_to_int(test_data[1]) test.desc = TEST_IDS[test.tid][1] # lookup the description from the table - - return test From 000095ccbc23ae61a638c42b1c34fbfb1d663b41 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 30 Jun 2016 13:38:19 -0400 Subject: [PATCH 028/128] finished first pass of UAS ID table --- obd/UnitsAndScaling.py | 64 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/obd/UnitsAndScaling.py b/obd/UnitsAndScaling.py index 3c2e8875..23232643 100644 --- a/obd/UnitsAndScaling.py +++ b/obd/UnitsAndScaling.py @@ -39,6 +39,7 @@ Unit.define("ratio = []") Unit.define("gps = gram / second = GPS = grams_per_second") Unit.define("lph = liter / hour = LPH = liters_per_hour") +Unit.define("ppm = count / 1000000 = PPM = parts_per_million") @@ -66,6 +67,7 @@ def __call__(self, _bytes): # dict for looking up standardized UAS IDs with conversion objects UAS_IDS = { + # unsigned ----------------------------------------- 0x01 : UAS(False, 1, Unit.count), 0x02 : UAS(False, 0.1, Unit.count), 0x03 : UAS(False, 0.01, Unit.count), @@ -106,4 +108,66 @@ def __call__(self, _bytes): 0x26 : UAS(False, 0.1, Unit.millivolt / Unit.millisecond), 0x27 : UAS(False, 0.1, Unit.grams_per_second), 0x28 : UAS(False, 1, Unit.grams_per_second), + 0x29 : UAS(False, 0.25, Unit.pascal / Unit.second), + 0x2A : UAS(False, 0.001, Unit.kilogram / Unit.hour), + 0x2B : UAS(False, 1, Unit.count), + 0x2C : UAS(False, 0.01, Unit.gram), # TODO: per-cylinder + 0x2D : UAS(False, 0.01, Unit.milligram), # TODO: per-stroke + 0x2E : None, # TODO: True/False + 0x2F : UAS(False, 0.01, Unit.percent), + 0x30 : UAS(False, 0.001526, Unit.percent), + 0x31 : UAS(False, 0.001, Unit.liter), + 0x32 : UAS(False, 0.0000305, Unit.inch), + 0x33 : UAS(False, 0.00024414, Unit.count), # TODO: equivalence ration (lambda) + 0x34 : UAS(False, 1, Unit.minute), + 0x35 : UAS(False, 10, Unit.millisecond), + 0x36 : UAS(False, 0.01, Unit.gram), + 0x37 : UAS(False, 0.1, Unit.gram), + 0x38 : UAS(False, 1, Unit.gram), + 0x39 : UAS(False, 0.01, Unit.percent), # TODO: centered + 0x3A : UAS(False, 0.001, Unit.gram), + 0x3B : UAS(False, 0.0001, Unit.gram), + 0x3C : UAS(False, 0.1, Unit.microsecond), + 0x3D : UAS(False, 0.01, Unit.milliampere), + 0x3E : UAS(False, 0.00006103516, Unit.millimeter ** 2), + 0x3F : UAS(False, 0.01, Unit.liter), + 0x40 : UAS(False, 1, Unit.ppm), + 0x41 : UAS(False, 0.1, Unit.microampere), + + # signed ----------------------------------------- + 0x81 : UAS(True, 1, Unit.count), + 0x82 : UAS(True, 0.1, Unit.count), + 0x83 : UAS(True, 0.01, Unit.count), + 0x84 : UAS(True, 0.001, Unit.count), + 0x85 : UAS(True, 0.0000305, Unit.count), + 0x86 : UAS(True, 0.000305, Unit.count), + 0x87 : UAS(True, 1, Unit.ppm), + # + 0x8A : UAS(True, 0.122, Unit.millivolt), + 0x8B : UAS(True, 0.001, Unit.volt), + 0x8C : UAS(True, 0.01, Unit.volt), + 0x8D : UAS(True, 0.00390625, Unit.milliampere), + 0x8E : UAS(True, 0.001, Unit.ampere), + # + 0x90 : UAS(True, 1, Unit.millisecond), + # + 0x96 : UAS(True, 0.1, Unit.celsius), + # + 0x99 : UAS(True, 0.1, Unit.kilopascal), + # + 0x9C : UAS(True, 0.01, Unit.degree), + 0x9D : UAS(True, 0.5, Unit.degree), + # + 0xA8 : UAS(True, 1, Unit.grams_per_second), + 0xA9 : UAS(True, 0.25, Unit.pascal / Unit.second), + # + 0xAD : UAS(True, 0.01, Unit.milligram), # TODO: per-stroke + 0xAE : UAS(True, 0.1, Unit.milligram), # TODO: per-stroke + 0xAF : UAS(True, 0.01, Unit.percent), + 0xB0 : UAS(True, 0.003052, Unit.percent), + 0xB1 : UAS(True, 2, Unit.millivolt / Unit.second), + # + 0xFC : UAS(True, 0.01, Unit.kilopascal), + 0xFD : UAS(True, 0.001, Unit.kilopascal), + 0xFE : UAS(True, 0.25, Unit.pascal), } From d826a219a255d56576d56827f26c14aedfd09427 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 30 Jun 2016 13:48:06 -0400 Subject: [PATCH 029/128] finished remaining conversions --- obd/UnitsAndScaling.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/obd/UnitsAndScaling.py b/obd/UnitsAndScaling.py index 23232643..04ca0634 100644 --- a/obd/UnitsAndScaling.py +++ b/obd/UnitsAndScaling.py @@ -49,10 +49,11 @@ class UAS(): Used in the decoding of Mode 06 monitor responses """ - def __init__(self, signed, scale, unit): + def __init__(self, signed, scale, unit, offset=0): self.signed = signed self.scale = scale self.unit = unit + self.offset = offset def __call__(self, _bytes): value = bytes_to_int(_bytes) @@ -61,7 +62,7 @@ def __call__(self, _bytes): value = twos_comp(value, len(_bytes) * 8) value *= self.scale - + value += self.offset return Unit.Quantity(value, self.unit) @@ -89,7 +90,7 @@ def __call__(self, _bytes): 0x13 : UAS(False, 1, Unit.milliohm), 0x14 : UAS(False, 1, Unit.ohm), 0x15 : UAS(False, 1, Unit.kiloohm), - 0x16 : None, # TODO + 0x16 : UAS(False, 0.1, Unit.celsius, offset=-40.0), 0x17 : UAS(False, 0.01, Unit.kilopascal), 0x18 : UAS(False, 0.0117, Unit.kilopascal), 0x19 : UAS(False, 0.079, Unit.kilopascal), @@ -97,7 +98,7 @@ def __call__(self, _bytes): 0x1B : UAS(False, 10, Unit.kilopascal), 0x1C : UAS(False, 0.01, Unit.degree), 0x1D : UAS(False, 0.5, Unit.degree), - 0x1E : None, # TODO + 0x1E : UAS(False, 0.0000305, Unit.ratio), 0x1F : UAS(False, 0.05, Unit.ratio), 0x20 : UAS(False, 0.00390625, Unit.ratio), 0x21 : UAS(False, 1, Unit.millihertz), @@ -111,20 +112,20 @@ def __call__(self, _bytes): 0x29 : UAS(False, 0.25, Unit.pascal / Unit.second), 0x2A : UAS(False, 0.001, Unit.kilogram / Unit.hour), 0x2B : UAS(False, 1, Unit.count), - 0x2C : UAS(False, 0.01, Unit.gram), # TODO: per-cylinder - 0x2D : UAS(False, 0.01, Unit.milligram), # TODO: per-stroke - 0x2E : None, # TODO: True/False + 0x2C : UAS(False, 0.01, Unit.gram), # per-cylinder + 0x2D : UAS(False, 0.01, Unit.milligram), # per-stroke + 0x2E : lambda _bytes: any([ bool(x) for x in _bytes]) 0x2F : UAS(False, 0.01, Unit.percent), 0x30 : UAS(False, 0.001526, Unit.percent), 0x31 : UAS(False, 0.001, Unit.liter), 0x32 : UAS(False, 0.0000305, Unit.inch), - 0x33 : UAS(False, 0.00024414, Unit.count), # TODO: equivalence ration (lambda) + 0x33 : UAS(False, 0.00024414, Unit.ratio), 0x34 : UAS(False, 1, Unit.minute), 0x35 : UAS(False, 10, Unit.millisecond), 0x36 : UAS(False, 0.01, Unit.gram), 0x37 : UAS(False, 0.1, Unit.gram), 0x38 : UAS(False, 1, Unit.gram), - 0x39 : UAS(False, 0.01, Unit.percent), # TODO: centered + 0x39 : UAS(False, 0.01, Unit.percent, offset=-327.68), 0x3A : UAS(False, 0.001, Unit.gram), 0x3B : UAS(False, 0.0001, Unit.gram), 0x3C : UAS(False, 0.1, Unit.microsecond), @@ -161,8 +162,8 @@ def __call__(self, _bytes): 0xA8 : UAS(True, 1, Unit.grams_per_second), 0xA9 : UAS(True, 0.25, Unit.pascal / Unit.second), # - 0xAD : UAS(True, 0.01, Unit.milligram), # TODO: per-stroke - 0xAE : UAS(True, 0.1, Unit.milligram), # TODO: per-stroke + 0xAD : UAS(True, 0.01, Unit.milligram), # per-stroke + 0xAE : UAS(True, 0.1, Unit.milligram), # per-stroke 0xAF : UAS(True, 0.01, Unit.percent), 0xB0 : UAS(True, 0.003052, Unit.percent), 0xB1 : UAS(True, 2, Unit.millivolt / Unit.second), From 6ced0a231728637a530e75b07c001aab983f9aa0 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 30 Jun 2016 14:54:12 -0400 Subject: [PATCH 030/128] finished intial implementation of mode 06 decoder, fixed tests --- obd/UnitsAndScaling.py | 2 +- obd/decoders.py | 14 +++++++++++++- tests/test_OBDCommand.py | 2 +- tests/test_decoders.py | 2 +- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/obd/UnitsAndScaling.py b/obd/UnitsAndScaling.py index 04ca0634..f80cb701 100644 --- a/obd/UnitsAndScaling.py +++ b/obd/UnitsAndScaling.py @@ -114,7 +114,7 @@ def __call__(self, _bytes): 0x2B : UAS(False, 1, Unit.count), 0x2C : UAS(False, 0.01, Unit.gram), # per-cylinder 0x2D : UAS(False, 0.01, Unit.milligram), # per-stroke - 0x2E : lambda _bytes: any([ bool(x) for x in _bytes]) + 0x2E : lambda _bytes: any([ bool(x) for x in _bytes]), 0x2F : UAS(False, 0.01, Unit.percent), 0x30 : UAS(False, 0.001526, Unit.percent), 0x31 : UAS(False, 0.001, Unit.liter), diff --git a/obd/decoders.py b/obd/decoders.py index 319a53d9..b2a5cff2 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -33,7 +33,7 @@ from .utils import * from .codes import * from .OBDResponse import Status, Test, Monitor, MonitorTest -from .UnitsAndScaling import Unit +from .UnitsAndScaling import Unit, UAS_IDS import logging @@ -430,9 +430,21 @@ def dtc(messages): def monitor_test(d): test = MonitorTest() + + uas = UAS_IDS.get(bytes_to_int(test_data[2]), None) + + # if we can't decode the value, return a null MonitorTest + if uas is None: + return test + test.tid = bytes_to_int(test_data[1]) test.desc = TEST_IDS[test.tid][1] # lookup the description from the table + # convert the value and limits to actual values + test.value = uas(test_data[3:5]) + test.min = uas(test_data[5:7]) + test.max = uas(test_data[7:]) + return test diff --git a/tests/test_OBDCommand.py b/tests/test_OBDCommand.py index bad9b8b8..d97c02ad 100644 --- a/tests/test_OBDCommand.py +++ b/tests/test_OBDCommand.py @@ -1,6 +1,6 @@ from obd.OBDCommand import OBDCommand -from obd.OBDResponse import Unit +from obd.UnitsAndScaling import Unit from obd.decoders import noop from obd.protocols import * diff --git a/tests/test_decoders.py b/tests/test_decoders.py index dffed7cf..598508cf 100644 --- a/tests/test_decoders.py +++ b/tests/test_decoders.py @@ -1,7 +1,7 @@ from binascii import unhexlify -from obd.OBDResponse import Unit +from obd.UnitsAndScaling import Unit from obd.protocols.protocol import Frame, Message import obd.decoders as d From 04c49051f6824c17984f7235d1b216f63f27d0c0 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 30 Jun 2016 15:34:22 -0400 Subject: [PATCH 031/128] initial test of monitor decoder --- obd/OBDResponse.py | 18 ++++++++++++---- obd/decoders.py | 49 ++++++++++++++++++++++++------------------ tests/test_decoders.py | 6 ++++++ 3 files changed, 48 insertions(+), 25 deletions(-) diff --git a/obd/OBDResponse.py b/obd/OBDResponse.py index b5dbfea5..9a9fd037 100644 --- a/obd/OBDResponse.py +++ b/obd/OBDResponse.py @@ -99,13 +99,17 @@ def __init__(self): self.tests.append(test) def __str__(self): - valid_tests = [str(test) for test in tests if not test.is_null()] - return "\n".join(valid_tests) + valid_tests = [str(test) for test in self.tests if not test.is_null()] + if len(valid_tests) > 0: + return "\n".join(valid_tests) + else: + return "No tests to report" class MonitorTest(): def __init__(self): self.tid = None + self.name = None self.desc = None self.value = None self.min = None @@ -113,10 +117,16 @@ def __init__(self): @property def passed(self): - return (self.value >= self.min) and (self.value <= self.max) + if not self.is_null(): + return (self.value >= self.min) and (self.value <= self.max) + else: + return False def is_null(self): - return self.tid is None or self.value is None + return (self.tid is None or + self.value is None or + self.min is None or + self.max is None) def __str__(self): return "%s : %s [%s]" % (self.desc, diff --git a/obd/decoders.py b/obd/decoders.py index b2a5cff2..3a376d4f 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -316,19 +316,19 @@ def fuel_status(messages): v = d[0] # todo, support second fuel system if v <= 0: - logger.warning("Invalid fuel status response (v <= 0)") + logger.debug("Invalid fuel status response (v <= 0)") return None i = math.log(v, 2) # only a single bit should be on if i % 1 != 0: - logger.warning("Invalid fuel status response (multiple bits set)") + logger.debug("Invalid fuel status response (multiple bits set)") return None i = int(i) if i >= len(FUEL_STATUS): - logger.warning("Invalid fuel status response (no table entry)") + logger.debug("Invalid fuel status response (no table entry)") return None return FUEL_STATUS[i] @@ -339,19 +339,19 @@ def air_status(messages): v = d[0] if v <= 0: - logger.warning("Invalid air status response (v <= 0)") + logger.debug("Invalid air status response (v <= 0)") return None i = math.log(v, 2) # only a single bit should be on if i % 1 != 0: - logger.warning("Invalid air status response (multiple bits set)") + logger.debug("Invalid air status response (multiple bits set)") return None i = int(i) if i >= len(AIR_STATUS): - logger.warning("Invalid air status response (no table entry)") + logger.debug("Invalid air status response (no table entry)") return None return AIR_STATUS[i] @@ -428,24 +428,32 @@ def dtc(messages): return codes -def monitor_test(d): - test = MonitorTest() +def parse_monitor_test(d, mon): + tid = d[1] - uas = UAS_IDS.get(bytes_to_int(test_data[2]), None) + if tid not in TEST_IDS: + logger.debug("Encountered unknown Test ID") + return # if it's an unknown TID, abort - # if we can't decode the value, return a null MonitorTest - if uas is None: - return test + name = TEST_IDS[tid][0] # lookup the name from the table + desc = TEST_IDS[tid][1] # lookup the description from the table + + test = mon.__dict__[name] # use the "name" field to lookup the right test - test.tid = bytes_to_int(test_data[1]) - test.desc = TEST_IDS[test.tid][1] # lookup the description from the table + uas = UAS_IDS.get(d[2], None) - # convert the value and limits to actual values - test.value = uas(test_data[3:5]) - test.min = uas(test_data[5:7]) - test.max = uas(test_data[7:]) + # if we can't decode the value, return a null MonitorTest + if uas is None: + logger.debug("Encountered unknown Units and Scaling ID") + return - return test + # load the test results + test.tid = tid + test.name = name + test.desc = desc + test.value = uas(d[3:5]) # convert bytes to actual values + test.min = uas(d[5:7]) + test.max = uas(d[7:]) def monitor(messages): @@ -461,7 +469,6 @@ def monitor(messages): # look at data in blocks of 9 bytes (one test result) for n in range(0, len(d), 9): - test = monitor_test(d[n:n + 8]) # extract the 9 byte block, and parse a new MonitorTest - setattr(mon,test.name, test) # use the "name" field as the property + parse_monitor_test(d[n:n + 9], mon) # extract the 9 byte block, and parse a new MonitorTest return mon diff --git a/tests/test_decoders.py b/tests/test_decoders.py index 598508cf..f210f7ba 100644 --- a/tests/test_decoders.py +++ b/tests/test_decoders.py @@ -198,3 +198,9 @@ def test_dtc(): ("P0104", "Mass or Volume Air Flow Circuit Intermittent"), ("B0003", "Unknown error code"), ] + +def test_monitor(): + # v = d.monitor(m("01010A0BB00BB00BB00105100048000000640185240096004BFFFF")) + v = d.monitor(m("01010A0BB00BB00BB0")) + print(v) + assert(False) From 29ab768dc5b2bf88fd11b0f9c7f53424e960582d Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 30 Jun 2016 15:59:24 -0400 Subject: [PATCH 032/128] better implemenation and lookup tools for MonitorTests --- obd/OBDResponse.py | 33 +++++++++++++++++++++++++-------- obd/__init__.py | 2 +- obd/decoders.py | 28 ++++++++++++++++------------ tests/test_decoders.py | 4 ++-- 4 files changed, 44 insertions(+), 23 deletions(-) diff --git a/obd/OBDResponse.py b/obd/OBDResponse.py index 9a9fd037..9619a3e6 100644 --- a/obd/OBDResponse.py +++ b/obd/OBDResponse.py @@ -89,22 +89,39 @@ def __str__(self): class Monitor(): def __init__(self): - self.tests = [] + self._tests = {} # tid : MonitorTest + + # make the standard TIDs available as null monitor tests + # until real data comes it. This also prevents things from + # breaking when the user looks up a standard test that's null. + null_test = MonitorTest() - # make all TID tests available as properties for tid in TEST_IDS: name = TEST_IDS[tid][0] - test = MonitorTest() - self.__dict__[name] = test - self.tests.append(test) + self.__dict__[name] = null_test + self._tests[tid] = null_test + + def add_test(self, test): + self._tests[test.tid] = test + if test.name is not None: + self.__dict__[test.name] = test + + @property + def tests(self): + return [test for test in self._tests.values() if not test.is_null()] def __str__(self): - valid_tests = [str(test) for test in self.tests if not test.is_null()] - if len(valid_tests) > 0: - return "\n".join(valid_tests) + if len(self.tests) > 0: + return "\n".join([ str(t) for t in self.tests ]) else: return "No tests to report" + def __len__(self): + return len(self.tests) + + def __getitem__(self, tid): + return self._tests.get(tid, MonitorTest()) + class MonitorTest(): def __init__(self): diff --git a/obd/__init__.py b/obd/__init__.py index 6781af6c..d95f5815 100644 --- a/obd/__init__.py +++ b/obd/__init__.py @@ -49,7 +49,7 @@ import logging logger = logging.getLogger(__name__) -logger.setLevel(logging.WARNING) +logger.setLevel(logging.DEBUG) console_handler = logging.StreamHandler() # sends output to stderr console_handler.setFormatter(logging.Formatter("[%(name)s] %(message)s")) diff --git a/obd/decoders.py b/obd/decoders.py index 3a376d4f..fcd9aca0 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -429,32 +429,33 @@ def dtc(messages): def parse_monitor_test(d, mon): + test = MonitorTest() + tid = d[1] - if tid not in TEST_IDS: + if tid in TEST_IDS: + test.name = TEST_IDS[tid][0] # lookup the name from the table + test.desc = TEST_IDS[tid][1] # lookup the description from the table + else: logger.debug("Encountered unknown Test ID") - return # if it's an unknown TID, abort - - name = TEST_IDS[tid][0] # lookup the name from the table - desc = TEST_IDS[tid][1] # lookup the description from the table - - test = mon.__dict__[name] # use the "name" field to lookup the right test + test.name = "Unknown" + test.desc = "Unknown" uas = UAS_IDS.get(d[2], None) - # if we can't decode the value, return a null MonitorTest + # if we can't decode the value, abort if uas is None: logger.debug("Encountered unknown Units and Scaling ID") - return + return None # load the test results test.tid = tid - test.name = name - test.desc = desc test.value = uas(d[3:5]) # convert bytes to actual values test.min = uas(d[5:7]) test.max = uas(d[7:]) + return test + def monitor(messages): d = messages[0].data @@ -469,6 +470,9 @@ def monitor(messages): # look at data in blocks of 9 bytes (one test result) for n in range(0, len(d), 9): - parse_monitor_test(d[n:n + 9], mon) # extract the 9 byte block, and parse a new MonitorTest + # extract the 9 byte block, and parse a new MonitorTest + test = parse_monitor_test(d[n:n + 9], mon) + if test is not None: + mon.add_test(test) return mon diff --git a/tests/test_decoders.py b/tests/test_decoders.py index f210f7ba..ae3e0a0b 100644 --- a/tests/test_decoders.py +++ b/tests/test_decoders.py @@ -200,7 +200,7 @@ def test_dtc(): ] def test_monitor(): - # v = d.monitor(m("01010A0BB00BB00BB00105100048000000640185240096004BFFFF")) - v = d.monitor(m("01010A0BB00BB00BB0")) + v = d.monitor(m("01010A0BB00BB00BB00105100048000000640185240096004BFFFF")) + # v = d.monitor(m("01010A0BB00BB00BB0")) print(v) assert(False) From 4a4a783fc0b563322cd3cb85e22b650accb703e2 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 30 Jun 2016 16:57:23 -0400 Subject: [PATCH 033/128] wrote basic tests for monitor decoder --- obd/codes.py | 4 +-- tests/test_decoders.py | 63 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/obd/codes.py b/obd/codes.py index c453a139..69922c89 100644 --- a/obd/codes.py +++ b/obd/codes.py @@ -2209,8 +2209,8 @@ TEST_IDS = { # : # 0x0 is reserved - 0x01 : ("rtl_threshold voltage", "Rich to lean sensor threshold voltage"), - 0x02 : ("ltr_threshold voltage", "Lean to rich sensor threshold voltage"), + 0x01 : ("rtl_threshold_voltage", "Rich to lean sensor threshold voltage"), + 0x02 : ("ltr_threshold_voltage", "Lean to rich sensor threshold voltage"), 0x03 : ("low_voltage_switch_time", "Low sensor voltage for switch time calculation"), 0x04 : ("high_voltage_switch_time", "High sensor voltage for switch time calculation"), 0x05 : ("rtl_switch_time", "Rich to lean sensor switch time"), diff --git a/tests/test_decoders.py b/tests/test_decoders.py index ae3e0a0b..028b50f7 100644 --- a/tests/test_decoders.py +++ b/tests/test_decoders.py @@ -15,7 +15,7 @@ def m(hex_data, frames=[]): return [message] -FLOAT_EQUALS_TOLERANCE = 0.02 +FLOAT_EQUALS_TOLERANCE = 0.025 # comparison for pint floating point values def float_equals(va, vb): @@ -200,7 +200,62 @@ def test_dtc(): ] def test_monitor(): + # single test ----------------------------------------- + # [ test ] + v = d.monitor(m("01010A0BB00BB00BB0")) + assert len(v) == 1 # 1 test result + + # make sure we can look things up by name and TID + assert v[0x01] == v.rtl_threshold_voltage + + # make sure we got information + assert not v[0x01].is_null() + + assert float_equals(v[0x01].value, 365 * Unit.millivolt) + assert float_equals(v[0x01].min, 365 * Unit.millivolt) + assert float_equals(v[0x01].max, 365 * Unit.millivolt) + + # multiple tests -------------------------------------- + # [ test ][ test ][ test ] v = d.monitor(m("01010A0BB00BB00BB00105100048000000640185240096004BFFFF")) - # v = d.monitor(m("01010A0BB00BB00BB0")) - print(v) - assert(False) + assert len(v) == 3 # 3 test results + + # make sure we can look things up by name and TID + assert v[0x01] == v.rtl_threshold_voltage + assert v[0x05] == v.rtl_switch_time + + # make sure we got information + assert not v[0x01].is_null() + assert not v[0x05].is_null() + assert not v[0x85].is_null() + + assert float_equals(v[0x01].value, 365 * Unit.millivolt) + assert float_equals(v[0x01].min, 365 * Unit.millivolt) + assert float_equals(v[0x01].max, 365 * Unit.millivolt) + + assert float_equals(v[0x05].value, 72 * Unit.millisecond) + assert float_equals(v[0x05].min, 0 * Unit.millisecond) + assert float_equals(v[0x05].max, 100 * Unit.millisecond) + + assert float_equals(v[0x85].value, 150 * Unit.count) + assert float_equals(v[0x85].min, 75 * Unit.count) + assert float_equals(v[0x85].max, 65535 * Unit.count) + + # truncate incomplete tests ---------------------------- + # [ test ][junk] + v = d.monitor(m("01010A0BB00BB00BB0ABCDEF")) + assert len(v) == 1 # 1 test result + + # make sure we can look things up by name and TID + assert v[0x01] == v.rtl_threshold_voltage + + # make sure we got information + assert not v[0x01].is_null() + + assert float_equals(v[0x01].value, 365 * Unit.millivolt) + assert float_equals(v[0x01].min, 365 * Unit.millivolt) + assert float_equals(v[0x01].max, 365 * Unit.millivolt) + + # truncate incomplete tests ---------------------------- + v = d.monitor(m("01010A0BB00BB00B")) + assert len(v) == 0 # no valid tests From f90302d3786028fa57c5e5b7dff19bad02c0863c Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 30 Jun 2016 16:59:37 -0400 Subject: [PATCH 034/128] check that undefined tests are null --- tests/test_decoders.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_decoders.py b/tests/test_decoders.py index 028b50f7..d6407d7a 100644 --- a/tests/test_decoders.py +++ b/tests/test_decoders.py @@ -3,6 +3,7 @@ from obd.UnitsAndScaling import Unit from obd.protocols.protocol import Frame, Message +from obd.codes import TEST_IDS import obd.decoders as d @@ -259,3 +260,8 @@ def test_monitor(): # truncate incomplete tests ---------------------------- v = d.monitor(m("01010A0BB00BB00B")) assert len(v) == 0 # no valid tests + + # make sure that the standard tests are null + for tid in TEST_IDS: + name = TEST_IDS[tid][0] + assert v[tid].is_null() From 26ef4623aba6cfd9daa93c8819648795cbfa6a3b Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 30 Jun 2016 17:15:59 -0400 Subject: [PATCH 035/128] wired up all mode 06 commands to the monitor decoder --- obd/commands.py | 176 ++++++++++++++++++++++++------------------------ 1 file changed, 88 insertions(+), 88 deletions(-) diff --git a/obd/commands.py b/obd/commands.py index 04739de0..27f666c1 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -183,104 +183,104 @@ # Mode 06 calls PID's MID's (Monitor ID) # This is for CAN only # name description cmd bytes decoder ECU fast - OBDCommand("MIDS_A" , "Supported MIDs [01-20]" , b"0600", 0, pid, ECU.ALL, False), - OBDCommand("MON_O2_B1S1" , "O2 Sensor Monitor Bank 1 - Sensor 1" , b"0601", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_B1S2" , "O2 Sensor Monitor Bank 1 - Sensor 2" , b"0602", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_B1S3" , "O2 Sensor Monitor Bank 1 - Sensor 3" , b"0603", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_B1S4" , "O2 Sensor Monitor Bank 1 - Sensor 4" , b"0604", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_B2S1" , "O2 Sensor Monitor Bank 2 - Sensor 1" , b"0605", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_B2S2" , "O2 Sensor Monitor Bank 2 - Sensor 2" , b"0606", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_B2S3" , "O2 Sensor Monitor Bank 2 - Sensor 3" , b"0607", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_B2S4" , "O2 Sensor Monitor Bank 2 - Sensor 4" , b"0608", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_B3S1" , "O2 Sensor Monitor Bank 3 - Sensor 1" , b"0609", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_B3S2" , "O2 Sensor Monitor Bank 3 - Sensor 2" , b"060A", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_B3S3" , "O2 Sensor Monitor Bank 3 - Sensor 3" , b"060B", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_B3S4" , "O2 Sensor Monitor Bank 3 - Sensor 4" , b"060C", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_B4S1" , "O2 Sensor Monitor Bank 4 - Sensor 1" , b"060D", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_B4S2" , "O2 Sensor Monitor Bank 4 - Sensor 2" , b"060E", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_B4S3" , "O2 Sensor Monitor Bank 4 - Sensor 3" , b"060F", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_B4S4" , "O2 Sensor Monitor Bank 4 - Sensor 4" , b"0610", 0, drop, ECU.ALL, False), + OBDCommand("MIDS_A" , "Supported MIDs [01-20]" , b"0600", 0, pid, ECU.ALL, False), + OBDCommand("MONITOR_O2_B1S1" , "O2 Sensor Monitor Bank 1 - Sensor 1" , b"0601", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_B1S2" , "O2 Sensor Monitor Bank 1 - Sensor 2" , b"0602", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_B1S3" , "O2 Sensor Monitor Bank 1 - Sensor 3" , b"0603", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_B1S4" , "O2 Sensor Monitor Bank 1 - Sensor 4" , b"0604", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_B2S1" , "O2 Sensor Monitor Bank 2 - Sensor 1" , b"0605", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_B2S2" , "O2 Sensor Monitor Bank 2 - Sensor 2" , b"0606", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_B2S3" , "O2 Sensor Monitor Bank 2 - Sensor 3" , b"0607", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_B2S4" , "O2 Sensor Monitor Bank 2 - Sensor 4" , b"0608", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_B3S1" , "O2 Sensor Monitor Bank 3 - Sensor 1" , b"0609", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_B3S2" , "O2 Sensor Monitor Bank 3 - Sensor 2" , b"060A", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_B3S3" , "O2 Sensor Monitor Bank 3 - Sensor 3" , b"060B", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_B3S4" , "O2 Sensor Monitor Bank 3 - Sensor 4" , b"060C", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_B4S1" , "O2 Sensor Monitor Bank 4 - Sensor 1" , b"060D", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_B4S2" , "O2 Sensor Monitor Bank 4 - Sensor 2" , b"060E", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_B4S3" , "O2 Sensor Monitor Bank 4 - Sensor 3" , b"060F", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_B4S4" , "O2 Sensor Monitor Bank 4 - Sensor 4" , b"0610", 0, monitor, ECU.ALL, False), ] + ([None] * 15) + [ # 11 - 1F Reserved - OBDCommand("MIDS_B" , "Supported MIDs [21-40]" , b"0620", 0, pid, ECU.ALL, False), - OBDCommand("MON_CATALYST_B1" , "Catalyst Monitor Bank 1" , b"0621", 0, drop, ECU.ALL, False), - OBDCommand("MON_CATALYST_B2" , "Catalyst Monitor Bank 2" , b"0622", 0, drop, ECU.ALL, False), - OBDCommand("MON_CATALYST_B3" , "Catalyst Monitor Bank 3" , b"0623", 0, drop, ECU.ALL, False), - OBDCommand("MON_CATALYST_B4" , "Catalyst Monitor Bank 4" , b"0624", 0, drop, ECU.ALL, False), + OBDCommand("MIDS_B" , "Supported MIDs [21-40]" , b"0620", 0, pid, ECU.ALL, False), + OBDCommand("MONITOR_CATALYST_B1" , "Catalyst Monitor Bank 1" , b"0621", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_CATALYST_B2" , "Catalyst Monitor Bank 2" , b"0622", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_CATALYST_B3" , "Catalyst Monitor Bank 3" , b"0623", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_CATALYST_B4" , "Catalyst Monitor Bank 4" , b"0624", 0, monitor, ECU.ALL, False), ] + ([None] * 12) + [ # 25 - 30 Reserved - OBDCommand("MON_EGR_B1" , "EGR Monitor Bank 1" , b"0631", 0, drop, ECU.ALL, False), - OBDCommand("MON_EGR_B2" , "EGR Monitor Bank 2" , b"0632", 0, drop, ECU.ALL, False), - OBDCommand("MON_EGR_B3" , "EGR Monitor Bank 3" , b"0633", 0, drop, ECU.ALL, False), - OBDCommand("MON_EGR_B4" , "EGR Monitor Bank 4" , b"0634", 0, drop, ECU.ALL, False), - OBDCommand("MON_VVT_B1" , "VVT Monitor Bank 1" , b"0635", 0, drop, ECU.ALL, False), - OBDCommand("MON_VVT_B2" , "VVT Monitor Bank 2" , b"0636", 0, drop, ECU.ALL, False), - OBDCommand("MON_VVT_B3" , "VVT Monitor Bank 3" , b"0637", 0, drop, ECU.ALL, False), - OBDCommand("MON_VVT_B4" , "VVT Monitor Bank 4" , b"0638", 0, drop, ECU.ALL, False), - OBDCommand("MON_EVAP_150" , "EVAP Monitor (Cap Off / 0.150\")" , b"0639", 0, drop, ECU.ALL, False), - OBDCommand("MON_EVAP_090" , "EVAP Monitor (0.090\")" , b"063A", 0, drop, ECU.ALL, False), - OBDCommand("MON_EVAP_040" , "EVAP Monitor (0.040\")" , b"063B", 0, drop, ECU.ALL, False), - OBDCommand("MON_EVAP_020" , "EVAP Monitor (0.020\")" , b"063C", 0, drop, ECU.ALL, False), - OBDCommand("MON_PURGE_FLOW" , "Purge Flow Monitor" , b"063D", 0, drop, ECU.ALL, False), + OBDCommand("MONITOR_EGR_B1" , "EGR Monitor Bank 1" , b"0631", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_EGR_B2" , "EGR Monitor Bank 2" , b"0632", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_EGR_B3" , "EGR Monitor Bank 3" , b"0633", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_EGR_B4" , "EGR Monitor Bank 4" , b"0634", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_VVT_B1" , "VVT Monitor Bank 1" , b"0635", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_VVT_B2" , "VVT Monitor Bank 2" , b"0636", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_VVT_B3" , "VVT Monitor Bank 3" , b"0637", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_VVT_B4" , "VVT Monitor Bank 4" , b"0638", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_EVAP_150" , "EVAP Monitor (Cap Off / 0.150\")" , b"0639", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_EVAP_090" , "EVAP Monitor (0.090\")" , b"063A", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_EVAP_040" , "EVAP Monitor (0.040\")" , b"063B", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_EVAP_020" , "EVAP Monitor (0.020\")" , b"063C", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_PURGE_FLOW" , "Purge Flow Monitor" , b"063D", 0, monitor, ECU.ALL, False), ] + ([None] * 2) + [ # 3E - 3F Reserved - OBDCommand("MIDS_C" , "Supported MIDs [41-60]" , b"0640", 0, pid, ECU.ALL, False), - OBDCommand("MON_O2_HEATER_B1S1" , "O2 Sensor Heater Monitor Bank 1 - Sensor 1" , b"0641", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_HEATER_B1S2" , "O2 Sensor Heater Monitor Bank 1 - Sensor 2" , b"0642", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_HEATER_B1S3" , "O2 Sensor Heater Monitor Bank 1 - Sensor 3" , b"0643", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_HEATER_B1S4" , "O2 Sensor Heater Monitor Bank 1 - Sensor 4" , b"0644", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_HEATER_B2S1" , "O2 Sensor Heater Monitor Bank 2 - Sensor 1" , b"0645", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_HEATER_B2S2" , "O2 Sensor Heater Monitor Bank 2 - Sensor 2" , b"0646", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_HEATER_B2S3" , "O2 Sensor Heater Monitor Bank 2 - Sensor 3" , b"0647", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_HEATER_B2S4" , "O2 Sensor Heater Monitor Bank 2 - Sensor 4" , b"0648", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_HEATER_B3S1" , "O2 Sensor Heater Monitor Bank 3 - Sensor 1" , b"0649", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_HEATER_B3S2" , "O2 Sensor Heater Monitor Bank 3 - Sensor 2" , b"064A", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_HEATER_B3S3" , "O2 Sensor Heater Monitor Bank 3 - Sensor 3" , b"064B", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_HEATER_B3S4" , "O2 Sensor Heater Monitor Bank 3 - Sensor 4" , b"064C", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_HEATER_B4S1" , "O2 Sensor Heater Monitor Bank 4 - Sensor 1" , b"064D", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_HEATER_B4S2" , "O2 Sensor Heater Monitor Bank 4 - Sensor 2" , b"064E", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_HEATER_B4S3" , "O2 Sensor Heater Monitor Bank 4 - Sensor 3" , b"064F", 0, drop, ECU.ALL, False), - OBDCommand("MON_O2_HEATER_B4S4" , "O2 Sensor Heater Monitor Bank 4 - Sensor 4" , b"0650", 0, drop, ECU.ALL, False), + OBDCommand("MIDS_C" , "Supported MIDs [41-60]" , b"0640", 0, pid, ECU.ALL, False), + OBDCommand("MONITOR_O2_HEATER_B1S1" , "O2 Sensor Heater Monitor Bank 1 - Sensor 1" , b"0641", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_HEATER_B1S2" , "O2 Sensor Heater Monitor Bank 1 - Sensor 2" , b"0642", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_HEATER_B1S3" , "O2 Sensor Heater Monitor Bank 1 - Sensor 3" , b"0643", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_HEATER_B1S4" , "O2 Sensor Heater Monitor Bank 1 - Sensor 4" , b"0644", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_HEATER_B2S1" , "O2 Sensor Heater Monitor Bank 2 - Sensor 1" , b"0645", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_HEATER_B2S2" , "O2 Sensor Heater Monitor Bank 2 - Sensor 2" , b"0646", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_HEATER_B2S3" , "O2 Sensor Heater Monitor Bank 2 - Sensor 3" , b"0647", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_HEATER_B2S4" , "O2 Sensor Heater Monitor Bank 2 - Sensor 4" , b"0648", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_HEATER_B3S1" , "O2 Sensor Heater Monitor Bank 3 - Sensor 1" , b"0649", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_HEATER_B3S2" , "O2 Sensor Heater Monitor Bank 3 - Sensor 2" , b"064A", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_HEATER_B3S3" , "O2 Sensor Heater Monitor Bank 3 - Sensor 3" , b"064B", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_HEATER_B3S4" , "O2 Sensor Heater Monitor Bank 3 - Sensor 4" , b"064C", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_HEATER_B4S1" , "O2 Sensor Heater Monitor Bank 4 - Sensor 1" , b"064D", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_HEATER_B4S2" , "O2 Sensor Heater Monitor Bank 4 - Sensor 2" , b"064E", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_HEATER_B4S3" , "O2 Sensor Heater Monitor Bank 4 - Sensor 3" , b"064F", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_O2_HEATER_B4S4" , "O2 Sensor Heater Monitor Bank 4 - Sensor 4" , b"0650", 0, monitor, ECU.ALL, False), ] + ([None] * 15) + [ # 51 - 5F Reserved - OBDCommand("MIDS_D" , "Supported MIDs [61-80]" , b"0660", 0, pid, ECU.ALL, False), - OBDCommand("MON_HEATED_CATALYST_B1" , "Heated Catalyst Monitor Bank 1" , b"0661", 0, drop, ECU.ALL, False), - OBDCommand("MON_HEATED_CATALYST_B2" , "Heated Catalyst Monitor Bank 2" , b"0662", 0, drop, ECU.ALL, False), - OBDCommand("MON_HEATED_CATALYST_B3" , "Heated Catalyst Monitor Bank 3" , b"0663", 0, drop, ECU.ALL, False), - OBDCommand("MON_HEATED_CATALYST_B4" , "Heated Catalyst Monitor Bank 4" , b"0664", 0, drop, ECU.ALL, False), + OBDCommand("MIDS_D" , "Supported MIDs [61-80]" , b"0660", 0, pid, ECU.ALL, False), + OBDCommand("MONITOR_HEATED_CATALYST_B1" , "Heated Catalyst Monitor Bank 1" , b"0661", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_HEATED_CATALYST_B2" , "Heated Catalyst Monitor Bank 2" , b"0662", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_HEATED_CATALYST_B3" , "Heated Catalyst Monitor Bank 3" , b"0663", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_HEATED_CATALYST_B4" , "Heated Catalyst Monitor Bank 4" , b"0664", 0, monitor, ECU.ALL, False), ] + ([None] * 12) + [ # 65 - 70 Reserved - OBDCommand("MON_SECONDARY_AIR_1" , "Secondary Air Monitor 1" , b"0671", 0, drop, ECU.ALL, False), - OBDCommand("MON_SECONDARY_AIR_2" , "Secondary Air Monitor 2" , b"0672", 0, drop, ECU.ALL, False), - OBDCommand("MON_SECONDARY_AIR_3" , "Secondary Air Monitor 3" , b"0673", 0, drop, ECU.ALL, False), - OBDCommand("MON_SECONDARY_AIR_4" , "Secondary Air Monitor 4" , b"0674", 0, drop, ECU.ALL, False), + OBDCommand("MONITOR_SECONDARY_AIR_1" , "Secondary Air Monitor 1" , b"0671", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_SECONDARY_AIR_2" , "Secondary Air Monitor 2" , b"0672", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_SECONDARY_AIR_3" , "Secondary Air Monitor 3" , b"0673", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_SECONDARY_AIR_4" , "Secondary Air Monitor 4" , b"0674", 0, monitor, ECU.ALL, False), ] + ([None] * 11) + [ # 75 - 7F Reserved - OBDCommand("MIDS_E" , "Supported MIDs [81-A0]" , b"0680", 0, pid, ECU.ALL, False), - OBDCommand("MON_FUEL_SYSTEM_B1" , "Fuel System Monitor Bank 1" , b"0681", 0, drop, ECU.ALL, False), - OBDCommand("MON_FUEL_SYSTEM_B2" , "Fuel System Monitor Bank 2" , b"0682", 0, drop, ECU.ALL, False), - OBDCommand("MON_FUEL_SYSTEM_B3" , "Fuel System Monitor Bank 3" , b"0683", 0, drop, ECU.ALL, False), - OBDCommand("MON_FUEL_SYSTEM_B4" , "Fuel System Monitor Bank 4" , b"0684", 0, drop, ECU.ALL, False), - OBDCommand("MON_BOOST_PRESSURE_B1" , "Boost Pressure Control Monitor Bank 1" , b"0685", 0, drop, ECU.ALL, False), - OBDCommand("MON_BOOST_PRESSURE_B2" , "Boost Pressure Control Monitor Bank 1" , b"0686", 0, drop, ECU.ALL, False), + OBDCommand("MIDS_E" , "Supported MIDs [81-A0]" , b"0680", 0, pid, ECU.ALL, False), + OBDCommand("MONITOR_FUEL_SYSTEM_B1" , "Fuel System Monitor Bank 1" , b"0681", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_FUEL_SYSTEM_B2" , "Fuel System Monitor Bank 2" , b"0682", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_FUEL_SYSTEM_B3" , "Fuel System Monitor Bank 3" , b"0683", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_FUEL_SYSTEM_B4" , "Fuel System Monitor Bank 4" , b"0684", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_BOOST_PRESSURE_B1" , "Boost Pressure Control Monitor Bank 1" , b"0685", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_BOOST_PRESSURE_B2" , "Boost Pressure Control Monitor Bank 1" , b"0686", 0, monitor, ECU.ALL, False), ] + ([None] * 9) + [ # 87 - 8F Reserved - OBDCommand("MON_NOX_ABSORBER_B1" , "NOx Absorber Monitor Bank 1" , b"0690", 0, drop, ECU.ALL, False), - OBDCommand("MON_NOX_ABSORBER_B2" , "NOx Absorber Monitor Bank 2" , b"0691", 0, drop, ECU.ALL, False), + OBDCommand("MONITOR_NOX_ABSORBER_B1" , "NOx Absorber Monitor Bank 1" , b"0690", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_NOX_ABSORBER_B2" , "NOx Absorber Monitor Bank 2" , b"0691", 0, monitor, ECU.ALL, False), ] + ([None] * 6) + [ # 92 - 97 Reserved - OBDCommand("MON_NOX_CATALYST_B1" , "NOx Catalyst Monitor Bank 1" , b"0698", 0, drop, ECU.ALL, False), - OBDCommand("MON_NOX_CATALYST_B2" , "NOx Catalyst Monitor Bank 2" , b"0699", 0, drop, ECU.ALL, False), + OBDCommand("MONITOR_NOX_CATALYST_B1" , "NOx Catalyst Monitor Bank 1" , b"0698", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_NOX_CATALYST_B2" , "NOx Catalyst Monitor Bank 2" , b"0699", 0, monitor, ECU.ALL, False), ] + ([None] * 6) + [ # 9A - 9F Reserved - OBDCommand("MIDS_F" , "Supported MIDs [A1-C0]" , b"06A0", 0, pid, ECU.ALL, False), - OBDCommand("MON_MISFIRE_GENERAL" , "Misfire Monitor General Data" , b"06A1", 0, drop, ECU.ALL, False), - OBDCommand("MON_MISFIRE_CYLINDER_1" , "Misfire Cylinder 1 Data" , b"06A2", 0, drop, ECU.ALL, False), - OBDCommand("MON_MISFIRE_CYLINDER_2" , "Misfire Cylinder 2 Data" , b"06A3", 0, drop, ECU.ALL, False), - OBDCommand("MON_MISFIRE_CYLINDER_3" , "Misfire Cylinder 3 Data" , b"06A4", 0, drop, ECU.ALL, False), - OBDCommand("MON_MISFIRE_CYLINDER_4" , "Misfire Cylinder 4 Data" , b"06A5", 0, drop, ECU.ALL, False), - OBDCommand("MON_MISFIRE_CYLINDER_5" , "Misfire Cylinder 5 Data" , b"06A6", 0, drop, ECU.ALL, False), - OBDCommand("MON_MISFIRE_CYLINDER_6" , "Misfire Cylinder 6 Data" , b"06A7", 0, drop, ECU.ALL, False), - OBDCommand("MON_MISFIRE_CYLINDER_7" , "Misfire Cylinder 7 Data" , b"06A8", 0, drop, ECU.ALL, False), - OBDCommand("MON_MISFIRE_CYLINDER_8" , "Misfire Cylinder 8 Data" , b"06A9", 0, drop, ECU.ALL, False), - OBDCommand("MON_MISFIRE_CYLINDER_9" , "Misfire Cylinder 9 Data" , b"06AA", 0, drop, ECU.ALL, False), - OBDCommand("MON_MISFIRE_CYLINDER_10" , "Misfire Cylinder 10 Data" , b"06AB", 0, drop, ECU.ALL, False), - OBDCommand("MON_MISFIRE_CYLINDER_11" , "Misfire Cylinder 11 Data" , b"06AC", 0, drop, ECU.ALL, False), - OBDCommand("MON_MISFIRE_CYLINDER_12" , "Misfire Cylinder 12 Data" , b"06AD", 0, drop, ECU.ALL, False), + OBDCommand("MIDS_F" , "Supported MIDs [A1-C0]" , b"06A0", 0, pid, ECU.ALL, False), + OBDCommand("MONITOR_MISFIRE_GENERAL" , "Misfire Monitor General Data" , b"06A1", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_MISFIRE_CYLINDER_1" , "Misfire Cylinder 1 Data" , b"06A2", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_MISFIRE_CYLINDER_2" , "Misfire Cylinder 2 Data" , b"06A3", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_MISFIRE_CYLINDER_3" , "Misfire Cylinder 3 Data" , b"06A4", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_MISFIRE_CYLINDER_4" , "Misfire Cylinder 4 Data" , b"06A5", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_MISFIRE_CYLINDER_5" , "Misfire Cylinder 5 Data" , b"06A6", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_MISFIRE_CYLINDER_6" , "Misfire Cylinder 6 Data" , b"06A7", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_MISFIRE_CYLINDER_7" , "Misfire Cylinder 7 Data" , b"06A8", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_MISFIRE_CYLINDER_8" , "Misfire Cylinder 8 Data" , b"06A9", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_MISFIRE_CYLINDER_9" , "Misfire Cylinder 9 Data" , b"06AA", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_MISFIRE_CYLINDER_10" , "Misfire Cylinder 10 Data" , b"06AB", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_MISFIRE_CYLINDER_11" , "Misfire Cylinder 11 Data" , b"06AC", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_MISFIRE_CYLINDER_12" , "Misfire Cylinder 12 Data" , b"06AD", 0, monitor, ECU.ALL, False), ] + ([None] * 2) + [ # AE - AF Reserved - OBDCommand("MON_PM_FILTER_B1" , "PM Filter Monitor Bank 1" , b"06B0", 0, drop, ECU.ALL, False), - OBDCommand("MON_PM_FILTER_B2" , "PM Filter Monitor Bank 2" , b"06B1", 0, drop, ECU.ALL, False), + OBDCommand("MONITOR_PM_FILTER_B1" , "PM Filter Monitor Bank 1" , b"06B0", 0, monitor, ECU.ALL, False), + OBDCommand("MONITOR_PM_FILTER_B2" , "PM Filter Monitor Bank 2" , b"06B1", 0, monitor, ECU.ALL, False), ] __mode7__ = [ From 00359ca31487a53cecc497d5d75028254eccc2b4 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 30 Jun 2016 17:48:18 -0400 Subject: [PATCH 036/128] added test for special handling of mode 06 in CAN --- tests/test_protocol_can.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/test_protocol_can.py b/tests/test_protocol_can.py index cfa10789..4392898f 100644 --- a/tests/test_protocol_can.py +++ b/tests/test_protocol_can.py @@ -201,6 +201,27 @@ def test_multi_line_mode_03(): check_message(r[0], len(test_case), 0, correct_data) +def test_multi_line_mode_06(): + """ + Tests the special handling of mode 6 commands. + The parser should chop off only the Mode byte from the response. + """ + + for protocol in CAN_11_PROTOCOLS: + p = protocol([]) + + test_case = [ + "7E8 10 0A 46 01 01 0A 0B B0", + "7E8 21 0B B0 0B B0", + ] + + correct_data = [0x01, 0x01, 0x0A, 0x0B, 0xB0, 0x0B, 0xB0, 0x0B, 0xB0] + + r = p(test_case) + assert len(r) == 1 + check_message(r[0], len(test_case), 0, correct_data) + + def test_can_29(): pass From 2bc60d69dfa991fbe069bd891d22ec187473cc92 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 30 Jun 2016 21:07:25 -0400 Subject: [PATCH 037/128] decided the old groups (by PID GET) was better --- obd/commands.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/obd/commands.py b/obd/commands.py index 27f666c1..16ac1347 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -66,8 +66,6 @@ OBDCommand("SPEED" , "Vehicle Speed" , b"010D", 1, speed, ECU.ENGINE, True), OBDCommand("TIMING_ADVANCE" , "Timing Advance" , b"010E", 1, timing_advance, ECU.ENGINE, True), OBDCommand("INTAKE_TEMP" , "Intake Air Temp" , b"010F", 1, temp, ECU.ENGINE, True), - - # name description cmd bytes decoder ECU fast OBDCommand("MAF" , "Air Flow Rate (MAF)" , b"0110", 2, maf, ECU.ENGINE, True), OBDCommand("THROTTLE_POS" , "Throttle Position" , b"0111", 1, percent, ECU.ENGINE, True), OBDCommand("AIR_STATUS" , "Secondary Air Status" , b"0112", 1, air_status, ECU.ENGINE, True), @@ -102,8 +100,6 @@ OBDCommand("EGR_ERROR" , "EGR Error" , b"012D", 1, percent_centered, ECU.ENGINE, True), OBDCommand("EVAPORATIVE_PURGE" , "Commanded Evaporative Purge" , b"012E", 1, percent, ECU.ENGINE, True), OBDCommand("FUEL_LEVEL" , "Fuel Level Input" , b"012F", 1, percent, ECU.ENGINE, True), - - # name description cmd bytes decoder ECU fast OBDCommand("WARMUPS_SINCE_DTC_CLEAR" , "Number of warm-ups since codes cleared" , b"0130", 1, count, ECU.ENGINE, True), OBDCommand("DISTANCE_SINCE_DTC_CLEAR" , "Distance traveled since codes cleared" , b"0131", 2, distance, ECU.ENGINE, True), OBDCommand("EVAP_VAPOR_PRESSURE" , "Evaporative system vapor pressure" , b"0132", 2, evap_pressure, ECU.ENGINE, True), @@ -138,8 +134,6 @@ OBDCommand("RUN_TIME_MIL" , "Time run with MIL on" , b"014D", 2, minutes, ECU.ENGINE, True), OBDCommand("TIME_SINCE_DTC_CLEARED" , "Time since trouble codes cleared" , b"014E", 2, minutes, ECU.ENGINE, True), OBDCommand("MAX_VALUES" , "Various Max values" , b"014F", 4, drop, ECU.ENGINE, True), # todo: decode this - - # name description cmd bytes decoder ECU fast OBDCommand("MAX_MAF" , "Maximum value for mass air flow sensor" , b"0150", 4, max_maf, ECU.ENGINE, True), OBDCommand("FUEL_TYPE" , "Fuel Type" , b"0151", 1, fuel_type, ECU.ENGINE, True), OBDCommand("ETHANOL_PERCENT" , "Ethanol Fuel Percent" , b"0152", 1, percent, ECU.ENGINE, True), From 9d104ef9bbb2935a21d5b7b8b7f1e0377a081fe1 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 30 Jun 2016 21:13:27 -0400 Subject: [PATCH 038/128] use existing DTC decoder to report the freeze DTC --- obd/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obd/commands.py b/obd/commands.py index 16ac1347..dd068154 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -52,7 +52,7 @@ # name description cmd bytes decoder ECU fast OBDCommand("PIDS_A" , "Supported PIDs [01-20]" , b"0100", 4, pid, ECU.ENGINE, True), OBDCommand("STATUS" , "Status since DTCs cleared" , b"0101", 4, status, ECU.ENGINE, True), - OBDCommand("FREEZE_DTC" , "Freeze DTC" , b"0102", 2, drop, ECU.ENGINE, True), + OBDCommand("FREEZE_DTC" , "Freeze DTC" , b"0102", 2, single_dtc, ECU.ENGINE, True), OBDCommand("FUEL_STATUS" , "Fuel System Status" , b"0103", 2, fuel_status, ECU.ENGINE, True), OBDCommand("ENGINE_LOAD" , "Calculated Engine Load" , b"0104", 1, percent, ECU.ENGINE, True), OBDCommand("COOLANT_TEMP" , "Engine Coolant Temperature" , b"0105", 1, temp, ECU.ENGINE, True), From 0af16eaf3f5f7a39740e38d8d8e72d75d1ef6e35 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 30 Jun 2016 21:19:07 -0400 Subject: [PATCH 039/128] removed unsupported warning from docs for FREEZE_DTC --- docs/Commands.md | 2 +- obd/commands.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Commands.md b/docs/Commands.md index ab670aee..0e8ce600 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -76,7 +76,7 @@ obd.commands.has_pid(1, 12) # True |----|---------------------------|-----------------------------------------| | 00 | PIDS_A | Supported PIDs [01-20] | | 01 | STATUS | Status since DTCs cleared | -| 02 | *unsupported* | *unsupported* | +| 02 | FREEZE_DTC | DTC that triggered the freeze frame | | 03 | FUEL_STATUS | Fuel System Status | | 04 | ENGINE_LOAD | Calculated Engine Load | | 05 | COOLANT_TEMP | Engine Coolant Temperature | diff --git a/obd/commands.py b/obd/commands.py index dd068154..f4595e35 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -52,7 +52,7 @@ # name description cmd bytes decoder ECU fast OBDCommand("PIDS_A" , "Supported PIDs [01-20]" , b"0100", 4, pid, ECU.ENGINE, True), OBDCommand("STATUS" , "Status since DTCs cleared" , b"0101", 4, status, ECU.ENGINE, True), - OBDCommand("FREEZE_DTC" , "Freeze DTC" , b"0102", 2, single_dtc, ECU.ENGINE, True), + OBDCommand("FREEZE_DTC" , "DTC that triggered the freeze frame" , b"0102", 2, single_dtc, ECU.ENGINE, True), OBDCommand("FUEL_STATUS" , "Fuel System Status" , b"0103", 2, fuel_status, ECU.ENGINE, True), OBDCommand("ENGINE_LOAD" , "Calculated Engine Load" , b"0104", 1, percent, ECU.ENGINE, True), OBDCommand("COOLANT_TEMP" , "Engine Coolant Temperature" , b"0105", 1, temp, ECU.ENGINE, True), From 7adcd1ae3b18e0fb72f5f9853629c70a540566e6 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 30 Jun 2016 21:40:10 -0400 Subject: [PATCH 040/128] implemented bit-encoded O2 sensor decoders --- obd/commands.py | 4 ++-- obd/decoders.py | 19 +++++++++++++++++++ tests/test_decoders.py | 12 ++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/obd/commands.py b/obd/commands.py index f4595e35..333297fe 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -69,7 +69,7 @@ OBDCommand("MAF" , "Air Flow Rate (MAF)" , b"0110", 2, maf, ECU.ENGINE, True), OBDCommand("THROTTLE_POS" , "Throttle Position" , b"0111", 1, percent, ECU.ENGINE, True), OBDCommand("AIR_STATUS" , "Secondary Air Status" , b"0112", 1, air_status, ECU.ENGINE, True), - OBDCommand("O2_SENSORS" , "O2 Sensors Present" , b"0113", 1, drop, ECU.ENGINE, True), + OBDCommand("O2_SENSORS" , "O2 Sensors Present" , b"0113", 1, o2_sensors, ECU.ENGINE, True), OBDCommand("O2_B1S1" , "O2: Bank 1 - Sensor 1 Voltage" , b"0114", 2, sensor_voltage, ECU.ENGINE, True), OBDCommand("O2_B1S2" , "O2: Bank 1 - Sensor 2 Voltage" , b"0115", 2, sensor_voltage, ECU.ENGINE, True), OBDCommand("O2_B1S3" , "O2: Bank 1 - Sensor 3 Voltage" , b"0116", 2, sensor_voltage, ECU.ENGINE, True), @@ -79,7 +79,7 @@ OBDCommand("O2_B2S3" , "O2: Bank 2 - Sensor 3 Voltage" , b"011A", 2, sensor_voltage, ECU.ENGINE, True), OBDCommand("O2_B2S4" , "O2: Bank 2 - Sensor 4 Voltage" , b"011B", 2, sensor_voltage, ECU.ENGINE, True), OBDCommand("OBD_COMPLIANCE" , "OBD Standards Compliance" , b"011C", 1, obd_compliance, ECU.ENGINE, True), - OBDCommand("O2_SENSORS_ALT" , "O2 Sensors Present (alternate)" , b"011D", 1, drop, ECU.ENGINE, True), + OBDCommand("O2_SENSORS_ALT" , "O2 Sensors Present (alternate)" , b"011D", 1, o2_sensors_alt, ECU.ENGINE, True), OBDCommand("AUX_INPUT_STATUS" , "Auxiliary input status" , b"011E", 1, drop, ECU.ENGINE, True), OBDCommand("RUN_TIME" , "Engine Run Time" , b"011F", 2, seconds, ECU.ENGINE, True), diff --git a/obd/decoders.py b/obd/decoders.py index fcd9aca0..c91ba2d5 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -244,6 +244,25 @@ def fuel_rate(messages): v = v * 0.05 return v * Unit.liters_per_hour +# special bit encoding for PID 13 +def o2_sensors(messages): + d = messages[0].data + bitstring = bytes_to_bits(d) + return ( + tuple([ b == "1" for b in bitstring[:4] ]), # bank 1 + tuple([ b == "1" for b in bitstring[4:] ]), # bank 2 + ) + +# special bit encoding for PID 1D +def o2_sensors_alt(messages): + d = messages[0].data + bitstring = bytes_to_bits(d) + return ( + tuple([ b == "1" for b in bitstring[:2] ]), # bank 1 + tuple([ b == "1" for b in bitstring[2:4] ]), # bank 2 + tuple([ b == "1" for b in bitstring[4:6] ]), # bank 3 + tuple([ b == "1" for b in bitstring[6:] ]), # bank 4 + ) def elm_voltage(messages): # doesn't register as a normal OBD response, diff --git a/tests/test_decoders.py b/tests/test_decoders.py index d6407d7a..07d3a944 100644 --- a/tests/test_decoders.py +++ b/tests/test_decoders.py @@ -165,6 +165,18 @@ def test_air_status(): assert d.air_status(m("08")) == "Pump commanded on for diagnostics" assert d.air_status(m("03")) == None +def test_o2_sensors(): + assert d.o2_sensors(m("00")) == ((False, False, False, False), (False, False, False, False)) + assert d.o2_sensors(m("01")) == ((False, False, False, False), (False, False, False, True)) + assert d.o2_sensors(m("0F")) == ((False, False, False, False), (True, True, True, True)) + assert d.o2_sensors(m("F0")) == ((True, True, True, True), (False, False, False, False)) + +def test_o2_sensors_alt(): + assert d.o2_sensors_alt(m("00")) == ((False, False), (False, False), (False, False), (False, False)) + assert d.o2_sensors_alt(m("01")) == ((False, False), (False, False), (False, False), (False, True)) + assert d.o2_sensors_alt(m("0F")) == ((False, False), (False, False), (True, True), (True, True)) + assert d.o2_sensors_alt(m("F0")) == ((True, True), (True, True), (False, False), (False, False)) + def test_elm_voltage(): # these aren't parsed as standard hex messages, so manufacture our own assert d.elm_voltage([ Message([ Frame("12.875") ]) ]) == 12.875 * Unit.volt From e7725ca22f086f9a92675f70a5fe59a08c65206f Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 30 Jun 2016 21:42:49 -0400 Subject: [PATCH 041/128] updated docs --- docs/Commands.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Commands.md b/docs/Commands.md index 0e8ce600..07fd4f47 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -93,7 +93,7 @@ obd.commands.has_pid(1, 12) # True | 10 | MAF | Air Flow Rate (MAF) | | 11 | THROTTLE_POS | Throttle Position | | 12 | AIR_STATUS | Secondary Air Status | -| 13 | *unsupported* | *unsupported* | +| 13 | O2_SENSORS | O2 Sensors Present | | 14 | O2_B1S1 | O2: Bank 1 - Sensor 1 Voltage | | 15 | O2_B1S2 | O2: Bank 1 - Sensor 2 Voltage | | 16 | O2_B1S3 | O2: Bank 1 - Sensor 3 Voltage | @@ -103,7 +103,7 @@ obd.commands.has_pid(1, 12) # True | 1A | O2_B2S3 | O2: Bank 2 - Sensor 3 Voltage | | 1B | O2_B2S4 | O2: Bank 2 - Sensor 4 Voltage | | 1C | OBD_COMPLIANCE | OBD Standards Compliance | -| 1D | *unsupported* | *unsupported* | +| 1D | O2_SENSORS_ALT | O2 Sensors Present (alternate) | | 1E | *unsupported* | *unsupported* | | 1F | RUN_TIME | Engine Run Time | | 20 | PIDS_B | Supported PIDs [21-40] | From 697c12678ae6e4d2f7deace2ed3274e3f2d87262 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 30 Jun 2016 21:56:37 -0400 Subject: [PATCH 042/128] implemented aux input status --- obd/commands.py | 2 +- obd/decoders.py | 4 ++++ tests/test_decoders.py | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/obd/commands.py b/obd/commands.py index 333297fe..b12e4cfb 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -80,7 +80,7 @@ OBDCommand("O2_B2S4" , "O2: Bank 2 - Sensor 4 Voltage" , b"011B", 2, sensor_voltage, ECU.ENGINE, True), OBDCommand("OBD_COMPLIANCE" , "OBD Standards Compliance" , b"011C", 1, obd_compliance, ECU.ENGINE, True), OBDCommand("O2_SENSORS_ALT" , "O2 Sensors Present (alternate)" , b"011D", 1, o2_sensors_alt, ECU.ENGINE, True), - OBDCommand("AUX_INPUT_STATUS" , "Auxiliary input status" , b"011E", 1, drop, ECU.ENGINE, True), + OBDCommand("AUX_INPUT_STATUS" , "Auxiliary input status" , b"011E", 1, aux_input_status, ECU.ENGINE, True), OBDCommand("RUN_TIME" , "Engine Run Time" , b"011F", 2, seconds, ECU.ENGINE, True), # name description cmd bytes decoder ECU fast diff --git a/obd/decoders.py b/obd/decoders.py index c91ba2d5..2a9e9447 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -253,6 +253,10 @@ def o2_sensors(messages): tuple([ b == "1" for b in bitstring[4:] ]), # bank 2 ) +def aux_input_status(messages): + d = messages[0].data + return ((d[0] >> 7) & 1) == 1 # first bit indicate PTO status + # special bit encoding for PID 1D def o2_sensors_alt(messages): d = messages[0].data diff --git a/tests/test_decoders.py b/tests/test_decoders.py index 07d3a944..cd6bdb4a 100644 --- a/tests/test_decoders.py +++ b/tests/test_decoders.py @@ -177,6 +177,10 @@ def test_o2_sensors_alt(): assert d.o2_sensors_alt(m("0F")) == ((False, False), (False, False), (True, True), (True, True)) assert d.o2_sensors_alt(m("F0")) == ((True, True), (True, True), (False, False), (False, False)) +def test_aux_input_status(): + assert d.aux_input_status(m("00")) == False + assert d.aux_input_status(m("80")) == True + def test_elm_voltage(): # these aren't parsed as standard hex messages, so manufacture our own assert d.elm_voltage([ Message([ Frame("12.875") ]) ]) == 12.875 * Unit.volt From 1d8f5f0727c49fb9147a1a2a43b31b615f172390 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 30 Jun 2016 21:59:07 -0400 Subject: [PATCH 043/128] updated docs --- docs/Commands.md | 2 +- obd/commands.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Commands.md b/docs/Commands.md index 07fd4f47..8d422bf3 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -104,7 +104,7 @@ obd.commands.has_pid(1, 12) # True | 1B | O2_B2S4 | O2: Bank 2 - Sensor 4 Voltage | | 1C | OBD_COMPLIANCE | OBD Standards Compliance | | 1D | O2_SENSORS_ALT | O2 Sensors Present (alternate) | -| 1E | *unsupported* | *unsupported* | +| 1E | AUX_INPUT_STATUS | Auxiliary input status (power take off) | | 1F | RUN_TIME | Engine Run Time | | 20 | PIDS_B | Supported PIDs [21-40] | | 21 | DISTANCE_W_MIL | Distance Traveled with MIL on | diff --git a/obd/commands.py b/obd/commands.py index b12e4cfb..19860fda 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -80,7 +80,7 @@ OBDCommand("O2_B2S4" , "O2: Bank 2 - Sensor 4 Voltage" , b"011B", 2, sensor_voltage, ECU.ENGINE, True), OBDCommand("OBD_COMPLIANCE" , "OBD Standards Compliance" , b"011C", 1, obd_compliance, ECU.ENGINE, True), OBDCommand("O2_SENSORS_ALT" , "O2 Sensors Present (alternate)" , b"011D", 1, o2_sensors_alt, ECU.ENGINE, True), - OBDCommand("AUX_INPUT_STATUS" , "Auxiliary input status" , b"011E", 1, aux_input_status, ECU.ENGINE, True), + OBDCommand("AUX_INPUT_STATUS" , "Auxiliary input status (power take off)" , b"011E", 1, aux_input_status, ECU.ENGINE, True), OBDCommand("RUN_TIME" , "Engine Run Time" , b"011F", 2, seconds, ECU.ENGINE, True), # name description cmd bytes decoder ECU fast From ec048adb2d07ed730cbc05387c1f44503a99aaf6 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Fri, 1 Jul 2016 00:56:38 -0400 Subject: [PATCH 044/128] removed old bitstring util --- obd/utils.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/obd/utils.py b/obd/utils.py index 015bc9bb..bdf57b51 100644 --- a/obd/utils.py +++ b/obd/utils.py @@ -83,12 +83,6 @@ def bytes_to_hex(bs): h += ("0" * (2 - len(bh))) + bh return h -def bitstring(_hex, bits=None): - b = bin(unhex(_hex))[2:] - if bits is not None: - b = ('0' * (bits - len(b))) + b - return b - def bitToBool(_bit): return (_bit == '1') From 3fa53d0e5d4057be2b8df6cc532d400e3ec74f9f Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Sun, 3 Jul 2016 13:14:45 -0400 Subject: [PATCH 045/128] removed old unhex util --- obd/OBDCommand.py | 4 ++-- obd/decoders.py | 4 ++-- obd/utils.py | 4 ---- tests/test_protocol.py | 9 ++++----- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/obd/OBDCommand.py b/obd/OBDCommand.py index c007816a..1426997c 100644 --- a/obd/OBDCommand.py +++ b/obd/OBDCommand.py @@ -67,14 +67,14 @@ def clone(self): @property def mode(self): if len(self.command) >= 2: - return unhex(self.command[:2]) + return int(self.command[:2], 16) else: return 0 @property def pid(self): if len(self.command) > 2: - return unhex(self.command[2:]) + return int(self.command[2:], 16) else: return 0 diff --git a/obd/decoders.py b/obd/decoders.py index 2a9e9447..8442f30e 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -160,8 +160,8 @@ def fuel_pres_direct(messages): def evap_pressure(messages): # decode the twos complement d = messages[0].data - a = twos_comp(unhex(d[0]), 8) - b = twos_comp(unhex(d[1]), 8) + a = twos_comp(d[0], 8) + b = twos_comp(d[1], 8) v = ((a * 256.0) + b) / 4.0 return v * Unit.pascal diff --git a/obd/utils.py b/obd/utils.py index bdf57b51..08ed6900 100644 --- a/obd/utils.py +++ b/obd/utils.py @@ -53,10 +53,6 @@ class OBDStatus: def num_bits_set(n): return bin(n).count("1") -def unhex(_hex): - _hex = "0" if _hex == "" else _hex - return int(_hex, 16) - def unbin(_bin): return int(_bin, 2) diff --git a/tests/test_protocol.py b/tests/test_protocol.py index a7f95a1a..b623d58b 100644 --- a/tests/test_protocol.py +++ b/tests/test_protocol.py @@ -1,6 +1,5 @@ import random -from obd.utils import unhex from obd.protocols import * from obd.protocols.protocol import Frame, Message @@ -55,10 +54,10 @@ def test_message_hex(): message.data = b'\x00\x01\x02' assert message.hex() == b'000102' - assert unhex(message.hex()[0:2]) == 0x00 - assert unhex(message.hex()[2:4]) == 0x01 - assert unhex(message.hex()[4:6]) == 0x02 - assert unhex(message.hex()) == 0x000102 + assert int(message.hex()[0:2], 16) == 0x00 + assert int(message.hex()[2:4], 16) == 0x01 + assert int(message.hex()[4:6], 16) == 0x02 + assert int(message.hex(), 16) == 0x000102 def test_populate_ecu_map(): From be9bd8f012d4d7e82855c8c3f3e50709f9af80bc Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Sun, 3 Jul 2016 13:24:57 -0400 Subject: [PATCH 046/128] fixed case where all correctly sized messages were running the padding routine --- obd/OBDCommand.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obd/OBDCommand.py b/obd/OBDCommand.py index 1426997c..3b064856 100644 --- a/obd/OBDCommand.py +++ b/obd/OBDCommand.py @@ -112,7 +112,7 @@ def __constrain_message_data(self, message): # chop off the right side message.data = message.data[:self.bytes] logger.debug("Message was longer than expected. Trimmed message: " + repr(message.data)) - else: + elif len(message.data) < self.bytes: # pad the right with zeros message.data += (b'\x00' * (self.bytes - len(message.data))) logger.debug("Message was shorter than expected. Padded message: " + repr(message.data)) From a323ad44e3ab2e846250069e7f14a9acfc37c611 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Sun, 3 Jul 2016 14:13:27 -0400 Subject: [PATCH 047/128] replaced as many decoders as possible with calls to the UAS table --- obd/UnitsAndScaling.py | 2 +- obd/commands.py | 34 +++++++++--------- obd/decoders.py | 79 +++++++----------------------------------- 3 files changed, 31 insertions(+), 84 deletions(-) diff --git a/obd/UnitsAndScaling.py b/obd/UnitsAndScaling.py index f80cb701..f36fccb7 100644 --- a/obd/UnitsAndScaling.py +++ b/obd/UnitsAndScaling.py @@ -107,7 +107,7 @@ def __call__(self, _bytes): 0x24 : UAS(False, 1, Unit.count), 0x25 : UAS(False, 1, Unit.kilometer), 0x26 : UAS(False, 0.1, Unit.millivolt / Unit.millisecond), - 0x27 : UAS(False, 0.1, Unit.grams_per_second), + 0x27 : UAS(False, 0.01, Unit.grams_per_second), 0x28 : UAS(False, 1, Unit.grams_per_second), 0x29 : UAS(False, 0.25, Unit.pascal / Unit.second), 0x2A : UAS(False, 0.001, Unit.kilogram / Unit.hour), diff --git a/obd/commands.py b/obd/commands.py index 19860fda..d23bab5c 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -62,11 +62,11 @@ OBDCommand("LONG_FUEL_TRIM_2" , "Long Term Fuel Trim - Bank 2" , b"0109", 1, percent_centered, ECU.ENGINE, True), OBDCommand("FUEL_PRESSURE" , "Fuel Pressure" , b"010A", 1, fuel_pressure, ECU.ENGINE, True), OBDCommand("INTAKE_PRESSURE" , "Intake Manifold Pressure" , b"010B", 1, pressure, ECU.ENGINE, True), - OBDCommand("RPM" , "Engine RPM" , b"010C", 2, rpm, ECU.ENGINE, True), - OBDCommand("SPEED" , "Vehicle Speed" , b"010D", 1, speed, ECU.ENGINE, True), + OBDCommand("RPM" , "Engine RPM" , b"010C", 2, uas(0x07), ECU.ENGINE, True), + OBDCommand("SPEED" , "Vehicle Speed" , b"010D", 1, uas(0x09), ECU.ENGINE, True), OBDCommand("TIMING_ADVANCE" , "Timing Advance" , b"010E", 1, timing_advance, ECU.ENGINE, True), OBDCommand("INTAKE_TEMP" , "Intake Air Temp" , b"010F", 1, temp, ECU.ENGINE, True), - OBDCommand("MAF" , "Air Flow Rate (MAF)" , b"0110", 2, maf, ECU.ENGINE, True), + OBDCommand("MAF" , "Air Flow Rate (MAF)" , b"0110", 2, uas(0x27), ECU.ENGINE, True), OBDCommand("THROTTLE_POS" , "Throttle Position" , b"0111", 1, percent, ECU.ENGINE, True), OBDCommand("AIR_STATUS" , "Secondary Air Status" , b"0112", 1, air_status, ECU.ENGINE, True), OBDCommand("O2_SENSORS" , "O2 Sensors Present" , b"0113", 1, o2_sensors, ECU.ENGINE, True), @@ -81,13 +81,13 @@ OBDCommand("OBD_COMPLIANCE" , "OBD Standards Compliance" , b"011C", 1, obd_compliance, ECU.ENGINE, True), OBDCommand("O2_SENSORS_ALT" , "O2 Sensors Present (alternate)" , b"011D", 1, o2_sensors_alt, ECU.ENGINE, True), OBDCommand("AUX_INPUT_STATUS" , "Auxiliary input status (power take off)" , b"011E", 1, aux_input_status, ECU.ENGINE, True), - OBDCommand("RUN_TIME" , "Engine Run Time" , b"011F", 2, seconds, ECU.ENGINE, True), + OBDCommand("RUN_TIME" , "Engine Run Time" , b"011F", 2, uas(0x12), ECU.ENGINE, True), # name description cmd bytes decoder ECU fast OBDCommand("PIDS_B" , "Supported PIDs [21-40]" , b"0120", 4, pid, ECU.ENGINE, True), - OBDCommand("DISTANCE_W_MIL" , "Distance Traveled with MIL on" , b"0121", 2, distance, ECU.ENGINE, True), - OBDCommand("FUEL_RAIL_PRESSURE_VAC" , "Fuel Rail Pressure (relative to vacuum)" , b"0122", 2, fuel_pres_vac, ECU.ENGINE, True), - OBDCommand("FUEL_RAIL_PRESSURE_DIRECT" , "Fuel Rail Pressure (direct inject)" , b"0123", 2, fuel_pres_direct, ECU.ENGINE, True), + OBDCommand("DISTANCE_W_MIL" , "Distance Traveled with MIL on" , b"0121", 2, uas(0x25), ECU.ENGINE, True), + OBDCommand("FUEL_RAIL_PRESSURE_VAC" , "Fuel Rail Pressure (relative to vacuum)" , b"0122", 2, uas(0x19), ECU.ENGINE, True), + OBDCommand("FUEL_RAIL_PRESSURE_DIRECT" , "Fuel Rail Pressure (direct inject)" , b"0123", 2, uas(0x1B), ECU.ENGINE, True), OBDCommand("O2_S1_WR_VOLTAGE" , "02 Sensor 1 WR Lambda Voltage" , b"0124", 4, sensor_voltage_big, ECU.ENGINE, True), OBDCommand("O2_S2_WR_VOLTAGE" , "02 Sensor 2 WR Lambda Voltage" , b"0125", 4, sensor_voltage_big, ECU.ENGINE, True), OBDCommand("O2_S3_WR_VOLTAGE" , "02 Sensor 3 WR Lambda Voltage" , b"0126", 4, sensor_voltage_big, ECU.ENGINE, True), @@ -100,8 +100,8 @@ OBDCommand("EGR_ERROR" , "EGR Error" , b"012D", 1, percent_centered, ECU.ENGINE, True), OBDCommand("EVAPORATIVE_PURGE" , "Commanded Evaporative Purge" , b"012E", 1, percent, ECU.ENGINE, True), OBDCommand("FUEL_LEVEL" , "Fuel Level Input" , b"012F", 1, percent, ECU.ENGINE, True), - OBDCommand("WARMUPS_SINCE_DTC_CLEAR" , "Number of warm-ups since codes cleared" , b"0130", 1, count, ECU.ENGINE, True), - OBDCommand("DISTANCE_SINCE_DTC_CLEAR" , "Distance traveled since codes cleared" , b"0131", 2, distance, ECU.ENGINE, True), + OBDCommand("WARMUPS_SINCE_DTC_CLEAR" , "Number of warm-ups since codes cleared" , b"0130", 1, uas(0x01), ECU.ENGINE, True), + OBDCommand("DISTANCE_SINCE_DTC_CLEAR" , "Distance traveled since codes cleared" , b"0131", 2, uas(0x25), ECU.ENGINE, True), OBDCommand("EVAP_VAPOR_PRESSURE" , "Evaporative system vapor pressure" , b"0132", 2, evap_pressure, ECU.ENGINE, True), OBDCommand("BAROMETRIC_PRESSURE" , "Barometric Pressure" , b"0133", 1, pressure, ECU.ENGINE, True), OBDCommand("O2_S1_WR_CURRENT" , "02 Sensor 1 WR Lambda Current" , b"0134", 4, current_centered, ECU.ENGINE, True), @@ -112,10 +112,10 @@ OBDCommand("O2_S6_WR_CURRENT" , "02 Sensor 6 WR Lambda Current" , b"0139", 4, current_centered, ECU.ENGINE, True), OBDCommand("O2_S7_WR_CURRENT" , "02 Sensor 7 WR Lambda Current" , b"013A", 4, current_centered, ECU.ENGINE, True), OBDCommand("O2_S8_WR_CURRENT" , "02 Sensor 8 WR Lambda Current" , b"013B", 4, current_centered, ECU.ENGINE, True), - OBDCommand("CATALYST_TEMP_B1S1" , "Catalyst Temperature: Bank 1 - Sensor 1" , b"013C", 2, catalyst_temp, ECU.ENGINE, True), - OBDCommand("CATALYST_TEMP_B2S1" , "Catalyst Temperature: Bank 2 - Sensor 1" , b"013D", 2, catalyst_temp, ECU.ENGINE, True), - OBDCommand("CATALYST_TEMP_B1S2" , "Catalyst Temperature: Bank 1 - Sensor 2" , b"013E", 2, catalyst_temp, ECU.ENGINE, True), - OBDCommand("CATALYST_TEMP_B2S2" , "Catalyst Temperature: Bank 2 - Sensor 2" , b"013F", 2, catalyst_temp, ECU.ENGINE, True), + OBDCommand("CATALYST_TEMP_B1S1" , "Catalyst Temperature: Bank 1 - Sensor 1" , b"013C", 2, uas(0x16), ECU.ENGINE, True), + OBDCommand("CATALYST_TEMP_B2S1" , "Catalyst Temperature: Bank 2 - Sensor 1" , b"013D", 2, uas(0x16), ECU.ENGINE, True), + OBDCommand("CATALYST_TEMP_B1S2" , "Catalyst Temperature: Bank 1 - Sensor 2" , b"013E", 2, uas(0x16), ECU.ENGINE, True), + OBDCommand("CATALYST_TEMP_B2S2" , "Catalyst Temperature: Bank 2 - Sensor 2" , b"013F", 2, uas(0x16), ECU.ENGINE, True), # name description cmd bytes decoder ECU fast OBDCommand("PIDS_C" , "Supported PIDs [41-60]" , b"0140", 4, pid, ECU.ENGINE, True), @@ -131,8 +131,8 @@ OBDCommand("ACCELERATOR_POS_E" , "Accelerator pedal position E" , b"014A", 1, percent, ECU.ENGINE, True), OBDCommand("ACCELERATOR_POS_F" , "Accelerator pedal position F" , b"014B", 1, percent, ECU.ENGINE, True), OBDCommand("THROTTLE_ACTUATOR" , "Commanded throttle actuator" , b"014C", 1, percent, ECU.ENGINE, True), - OBDCommand("RUN_TIME_MIL" , "Time run with MIL on" , b"014D", 2, minutes, ECU.ENGINE, True), - OBDCommand("TIME_SINCE_DTC_CLEARED" , "Time since trouble codes cleared" , b"014E", 2, minutes, ECU.ENGINE, True), + OBDCommand("RUN_TIME_MIL" , "Time run with MIL on" , b"014D", 2, uas(0x34), ECU.ENGINE, True), + OBDCommand("TIME_SINCE_DTC_CLEARED" , "Time since trouble codes cleared" , b"014E", 2, uas(0x34), ECU.ENGINE, True), OBDCommand("MAX_VALUES" , "Various Max values" , b"014F", 4, drop, ECU.ENGINE, True), # todo: decode this OBDCommand("MAX_MAF" , "Maximum value for mass air flow sensor" , b"0150", 4, max_maf, ECU.ENGINE, True), OBDCommand("FUEL_TYPE" , "Fuel Type" , b"0151", 1, fuel_type, ECU.ENGINE, True), @@ -143,7 +143,7 @@ OBDCommand("LONG_O2_TRIM_B1" , "Long term secondary O2 trim - Bank 1" , b"0156", 2, percent_centered, ECU.ENGINE, True), OBDCommand("SHORT_O2_TRIM_B2" , "Short term secondary O2 trim - Bank 2" , b"0157", 2, percent_centered, ECU.ENGINE, True), OBDCommand("LONG_O2_TRIM_B2" , "Long term secondary O2 trim - Bank 2" , b"0158", 2, percent_centered, ECU.ENGINE, True), - OBDCommand("FUEL_RAIL_PRESSURE_ABS" , "Fuel rail pressure (absolute)" , b"0159", 2, fuel_pres_direct, ECU.ENGINE, True), + OBDCommand("FUEL_RAIL_PRESSURE_ABS" , "Fuel rail pressure (absolute)" , b"0159", 2, uas(0x1B), ECU.ENGINE, True), OBDCommand("RELATIVE_ACCEL_POS" , "Relative accelerator pedal position" , b"015A", 1, percent, ECU.ENGINE, True), OBDCommand("HYBRID_BATTERY_REMAINING" , "Hybrid battery pack remaining life" , b"015B", 1, percent, ECU.ENGINE, True), OBDCommand("OIL_TEMP" , "Engine oil temperature" , b"015C", 1, temp, ECU.ENGINE, True), @@ -285,7 +285,7 @@ __mode9__ = [ # name description cmd bytes decoder ECU fast OBDCommand("PIDS_9A" , "Supported PIDs [01-20]" , b"0900", 4, pid, ECU.ENGINE, True), - OBDCommand("VIN_MESSAGE_COUNT" , "VIN Message Count" , b"0901", 1, count, ECU.ENGINE, True), + OBDCommand("VIN_MESSAGE_COUNT" , "VIN Message Count" , b"0901", 1, uas(0x01), ECU.ENGINE, True), OBDCommand("VIN" , "Get Vehicle Identification Number" , b"0902", 20, raw_string, ECU.ENGINE, True), ] diff --git a/obd/decoders.py b/obd/decoders.py index 8442f30e..55470b0c 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -30,6 +30,7 @@ ######################################################################## import math +import functools from .utils import * from .codes import * from .OBDResponse import Status, Test, Monitor, MonitorTest @@ -39,7 +40,6 @@ logger = logging.getLogger(__name__) - ''' All decoders take the form: @@ -50,6 +50,17 @@ def (): ''' + +def uas(id): + """ get the corresponding decoder for this UAS ID """ + return functools.partial(decode_uas, id=id) + +def decode_uas(messages, id): + d = messages[0].data + return UAS_IDS[id](d) + + + # drop all messages, return None def drop(messages): return None @@ -75,11 +86,6 @@ def raw_string(messages): Return Value object with value and units ''' -def count(messages): - d = messages[0].data - v = bytes_to_int(d) - return v * Unit.count - # 0 to 100 % def percent(messages): d = messages[0].data @@ -101,13 +107,6 @@ def temp(messages): v = v - 40 return Unit.Quantity(v, Unit.celsius) # non-multiplicative unit -# -40 to 6513.5 C -def catalyst_temp(messages): - d = messages[0].data - v = bytes_to_int(d) - v = (v / 10.0) - 40 - return Unit.Quantity(v, Unit.celsius) # non-multiplicative unit - # -128 to 128 mA def current_centered(messages): d = messages[0].data @@ -118,8 +117,7 @@ def current_centered(messages): # 0 to 1.275 volts def sensor_voltage(messages): d = messages[0].data - v = d[0] - v = v / 200.0 + v = d[0] / 200.0 return v * Unit.volt # 0 to 8 volts @@ -142,20 +140,6 @@ def pressure(messages): v = d[0] return v * Unit.kilopascal -# 0 to 5177 kPa -def fuel_pres_vac(messages): - d = messages[0].data - v = bytes_to_int(d) - v = v * 0.079 - return v * Unit.kilopascal - -# 0 to 655,350 kPa -def fuel_pres_direct(messages): - d = messages[0].data - v = bytes_to_int(d) - v = v * 10 - return v * Unit.kilopascal - # -8192 to 8192 Pa def evap_pressure(messages): # decode the twos complement @@ -179,18 +163,6 @@ def evap_pressure_alt(messages): v = v - 32767 return v * Unit.pascal -# 0 to 16,383.75 RPM -def rpm(messages): - d = messages[0].data - v = bytes_to_int(d) / 4.0 - return v * Unit.rpm - -# 0 to 255 KPH -def speed(messages): - d = messages[0].data - v = bytes_to_int(d) - return v * Unit.kph - # -64 to 63.5 degrees def timing_advance(messages): d = messages[0].data @@ -205,13 +177,6 @@ def inject_timing(messages): v = (v - 26880) / 128.0 return v * Unit.degree -# 0 to 655.35 grams/sec -def maf(messages): - d = messages[0].data - v = bytes_to_int(d) - v = v / 100.0 - return v * Unit.gps - # 0 to 2550 grams/sec def max_maf(messages): d = messages[0].data @@ -219,24 +184,6 @@ def max_maf(messages): v = v * 10 return v * Unit.gps -# 0 to 65535 seconds -def seconds(messages): - d = messages[0].data - v = bytes_to_int(d) - return v * Unit.second - -# 0 to 65535 minutes -def minutes(messages): - d = messages[0].data - v = bytes_to_int(d) - return v * Unit.minute - -# 0 to 65535 km -def distance(messages): - d = messages[0].data - v = bytes_to_int(d) - return v * Unit.kilometer - # 0 to 3212 Liters/hour def fuel_rate(messages): d = messages[0].data From dd28a3d075cebbaac885f71b3bff7f23ca0a2e7a Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Sun, 3 Jul 2016 14:15:38 -0400 Subject: [PATCH 048/128] removed test for removed decoders --- tests/test_decoders.py | 41 ----------------------------------------- 1 file changed, 41 deletions(-) diff --git a/tests/test_decoders.py b/tests/test_decoders.py index cd6bdb4a..66b7dcc2 100644 --- a/tests/test_decoders.py +++ b/tests/test_decoders.py @@ -45,11 +45,6 @@ def test_pid(): assert d.pid(m("F00AA00F")) == "11110000000010101010000000001111" assert d.pid(m("11")) == "00010001" -def test_count(): - assert d.count(m("00")) == 0 * Unit.count - assert d.count(m("0F")) == 15 * Unit.count - assert d.count(m("03E8")) == 1000 * Unit.count - def test_percent(): assert d.percent(m("00")) == 0.0 * Unit.percent assert d.percent(m("FF")) == 100.0 * Unit.percent @@ -64,10 +59,6 @@ def test_temp(): assert d.temp(m("FF")) == Unit.Quantity(215, Unit.celsius) assert d.temp(m("03E8")) == Unit.Quantity(960, Unit.celsius) -def test_catalyst_temp(): - assert d.catalyst_temp(m("0000")) == Unit.Quantity(-40.0, Unit.celsius) - assert d.catalyst_temp(m("FFFF")) == Unit.Quantity(6513.5, Unit.celsius) - def test_current_centered(): assert d.current_centered(m("00000000")) == -128.0 * Unit.milliampere assert d.current_centered(m("00008000")) == 0.0 * Unit.milliampere @@ -93,14 +84,6 @@ def test_pressure(): assert d.pressure(m("00")) == 0 * Unit.kilopascal assert d.pressure(m("00")) == 0 * Unit.kilopascal -def test_fuel_pres_vac(): - assert d.fuel_pres_vac(m("0000")) == 0.0 * Unit.kilopascal - assert d.fuel_pres_vac(m("FFFF")) == 5177.265 * Unit.kilopascal - -def test_fuel_pres_direct(): - assert d.fuel_pres_direct(m("0000")) == 0 * Unit.kilopascal - assert d.fuel_pres_direct(m("FFFF")) == 655350 * Unit.kilopascal - def test_evap_pressure(): pass # TODO #assert d.evap_pressure(m("0000")) == 0.0 * Unit.PA) @@ -114,14 +97,6 @@ def test_evap_pressure_alt(): assert d.evap_pressure_alt(m("7FFF")) == 0 * Unit.pascal assert d.evap_pressure_alt(m("FFFF")) == 32768 * Unit.pascal -def test_rpm(): - assert d.rpm(m("0000")) == 0.0 * Unit.rpm - assert d.rpm(m("FFFF")) == 16383.75 * Unit.rpm - -def test_speed(): - assert d.speed(m("00")) == 0 * Unit.kph - assert d.speed(m("FF")) == 255 * Unit.kph - def test_timing_advance(): assert d.timing_advance(m("00")) == -64.0 * Unit.degrees assert d.timing_advance(m("FF")) == 63.5 * Unit.degrees @@ -130,27 +105,11 @@ def test_inject_timing(): assert d.inject_timing(m("0000")) == -210 * Unit.degrees assert float_equals(d.inject_timing(m("FFFF")), 302 * Unit.degrees) -def test_maf(): - assert d.maf(m("0000")) == 0.0 * Unit.grams_per_second - assert d.maf(m("FFFF")) == 655.35 * Unit.grams_per_second - def test_max_maf(): assert d.max_maf(m("00000000")) == 0 * Unit.grams_per_second assert d.max_maf(m("FF000000")) == 2550 * Unit.grams_per_second assert d.max_maf(m("00ABCDEF")) == 0 * Unit.grams_per_second # last 3 bytes are unused (should be disregarded) -def test_seconds(): - assert d.seconds(m("0000")) == 0 * Unit.second - assert d.seconds(m("FFFF")) == 65535 * Unit.second - -def test_minutes(): - assert d.minutes(m("0000")) == 0 * Unit.minute - assert d.minutes(m("FFFF")) == 65535 * Unit.minute - -def test_distance(): - assert d.distance(m("0000")) == 0 * Unit.kilometer - assert d.distance(m("FFFF")) == 65535 * Unit.kilometer - def test_fuel_rate(): assert d.fuel_rate(m("0000")) == 0.0 * Unit.liters_per_hour assert d.fuel_rate(m("FFFF")) == 3276.75 * Unit.liters_per_hour From 9e3dc4b76fe16ebfcea40de06a4960b1da3b6c41 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Sun, 3 Jul 2016 14:20:58 -0400 Subject: [PATCH 049/128] formatting and comments in decoders --- obd/decoders.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/obd/decoders.py b/obd/decoders.py index 55470b0c..74a1c0bf 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -51,16 +51,6 @@ def (): -def uas(id): - """ get the corresponding decoder for this UAS ID """ - return functools.partial(decode_uas, id=id) - -def decode_uas(messages, id): - d = messages[0].data - return UAS_IDS[id](d) - - - # drop all messages, return None def drop(messages): return None @@ -81,10 +71,26 @@ def pid(messages): def raw_string(messages): return "\n".join([m.raw() for m in messages]) -''' -Sensor decoders -Return Value object with value and units -''' + +""" +Some decoders are simple and are already implemented in the Units And Scaling +tables (used mainly for Mode 06). The uas() decoder is a wrapper for any +Unit/Scaling in that table, simply to avoid redundant code. +""" + +def uas(id): + """ get the corresponding decoder for this UAS ID """ + return functools.partial(decode_uas, id=id) + +def decode_uas(messages, id): + d = messages[0].data + return UAS_IDS[id](d) + + +""" +General sensor decoders +Return pint Quantities +""" # 0 to 100 % def percent(messages): From 09bfe428189777bd925bc308162757ac419909e9 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Sun, 3 Jul 2016 15:26:49 -0400 Subject: [PATCH 050/128] tweaked logging in elm327 --- obd/elm327.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/obd/elm327.py b/obd/elm327.py index bdad95d0..a6ebb4ae 100644 --- a/obd/elm327.py +++ b/obd/elm327.py @@ -330,6 +330,7 @@ def close(self): self.__protocol = None if self.__port is not None: + logger.info("closing port") self.__write(b"ATZ") self.__port.close() self.__port = None @@ -408,7 +409,7 @@ def __read(self): if not c: if attempts <= 0: - logger.info("Failed to read port, giving up") + logger.warning("Failed to read port, giving up") break logger.info("Failed to read port, trying again...") From b0f0403bf9257e6e1ad1ecce27ca7348116bdee7 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Sun, 3 Jul 2016 15:52:52 -0400 Subject: [PATCH 051/128] forgot to bump up the version in __version__ --- obd/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obd/__version__.py b/obd/__version__.py index 700be4cb..1f199f19 100644 --- a/obd/__version__.py +++ b/obd/__version__.py @@ -1,2 +1,2 @@ -__version__ = '0.5.0' +__version__ = '0.6.0' From 56b642d9aea896e81a37394d4a5203b46fc8c5b3 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Sun, 3 Jul 2016 16:03:47 -0400 Subject: [PATCH 052/128] read data in blocks, not single characters --- obd/elm327.py | 59 +++++++++++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/obd/elm327.py b/obd/elm327.py index a6ebb4ae..20c224fb 100644 --- a/obd/elm327.py +++ b/obd/elm327.py @@ -54,6 +54,8 @@ class ELM327: ecus() """ + ELM_PROMPT = b'>' + _SUPPORTED_PROTOCOLS = { #"0" : None, # Automatic Mode. This isn't an actual protocol. If the # ELM reports this, then we don't have enough @@ -397,46 +399,47 @@ def __read(self): accumulates characters until the prompt character is seen returns a list of [/r/n] delimited strings """ + if not self.__port: + logger.info("cannot perform __read() when unconnected") + return "" attempts = 2 - buffer = b'' + buffer = bytearray() - if self.__port: - while True: - c = self.__port.read(1) + while True: + data = self.__port.read(self.__port.in_waiting or 1) - # if nothing was recieved - if not c: + # if nothing was recieved + if not data: - if attempts <= 0: - logger.warning("Failed to read port, giving up") - break + if attempts <= 0: + logger.warning("Failed to read port, giving up") + break - logger.info("Failed to read port, trying again...") - attempts -= 1 - continue + logger.info("Failed to read port, trying again...") + attempts -= 1 + continue - # end on chevron (ELM prompt character) - if c == b'>': - break + buffer.extend(data) - # skip null characters (ELM spec page 9) - if c == b'\x00': - continue + # end on chevron (ELM prompt character) + if self.ELM_PROMPT in buffer: + break - buffer += c # whatever is left must be part of the response - else: - logger.info("cannot perform __read() when unconnected") - return "" + # log, and remove the "bytearray( ... )" part + logger.debug("read: " + repr(buffer)[10:-1]) + + # clean out any null characters + buffer = re.sub(b"\x00", b"", buffer) - logger.debug("read: " + repr(buffer)) + # remove the prompt character + if buffer.endswith(self.ELM_PROMPT): + buffer = buffer[:-1] # convert bytes into a standard string - raw = buffer.decode() + string = buffer.decode() - # splits into lines - # removes empty lines - # removes trailing spaces - lines = [ s.strip() for s in re.split("[\r\n]", raw) if bool(s) ] + # splits into lines while removing empty lines and trailing spaces + lines = [ s.strip() for s in re.split("[\r\n]", string) if bool(s) ] return lines From 58d01d067562e6de93b942abf239870b3e596df1 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Sun, 3 Jul 2016 17:04:24 -0400 Subject: [PATCH 053/128] decreased wait time in obdsim test --- tests/test_obdsim.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_obdsim.py b/tests/test_obdsim.py index abf4c429..3d856a95 100644 --- a/tests/test_obdsim.py +++ b/tests/test_obdsim.py @@ -3,7 +3,7 @@ import pytest from obd import commands, Unit -STANDARD_WAIT_TIME = 0.25 +STANDARD_WAIT_TIME = 0.1 @pytest.fixture(scope="module") From 303e6bebe4001d786c17a5238f8a295107489c97 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Sun, 3 Jul 2016 17:12:34 -0400 Subject: [PATCH 054/128] skip obdsim tests if no port was specified --- tests/test_obdsim.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/tests/test_obdsim.py b/tests/test_obdsim.py index 3d856a95..3dc46fb4 100644 --- a/tests/test_obdsim.py +++ b/tests/test_obdsim.py @@ -11,12 +11,6 @@ def obd(request): """provides an OBD connection object for obdsim""" import obd port = request.config.getoption("--port") - - # TODO: lookup how to fail inside of a fixture - if port is None: - print("Please run obdsim and use --port=") - exit(1) - return obd.OBD(port) @@ -25,23 +19,22 @@ def async(request): """provides an OBD *Async* connection object for obdsim""" import obd port = request.config.getoption("--port") - - # TODO: lookup how to fail inside of a fixture - if port is None: - print("Please run obdsim and use --port=") - exit(1) - return obd.Async(port) def good_rpm_response(r): return (r.value.u == Unit.rpm) and (r.value >= 0.0 * Unit.rpm) + +@pytest.mark.skipif(not pytest.config.getoption("--port"), + reason="needs --port= to run") def test_supports(obd): assert(len(obd.supported_commands) > 0) assert(obd.supports(commands.RPM)) +@pytest.mark.skipif(not pytest.config.getoption("--port"), + reason="needs --port= to run") def test_rpm(obd): r = obd.query(commands.RPM) assert(good_rpm_response(r)) @@ -49,6 +42,8 @@ def test_rpm(obd): # Async tests +@pytest.mark.skipif(not pytest.config.getoption("--port"), + reason="needs --port= to run") def test_async_query(async): rs = [] @@ -67,6 +62,8 @@ def test_async_query(async): assert(all([ good_rpm_response(r) for r in rs ])) +@pytest.mark.skipif(not pytest.config.getoption("--port"), + reason="needs --port= to run") def test_async_callback(async): rs = [] @@ -81,6 +78,8 @@ def test_async_callback(async): assert(all([ good_rpm_response(r) for r in rs ])) +@pytest.mark.skipif(not pytest.config.getoption("--port"), + reason="needs --port= to run") def test_async_paused(async): assert(not async.running) @@ -97,6 +96,8 @@ def test_async_paused(async): assert(not async.running) +@pytest.mark.skipif(not pytest.config.getoption("--port"), + reason="needs --port= to run") def test_async_unwatch(async): watched_rs = [] @@ -127,6 +128,8 @@ def test_async_unwatch(async): assert(all([ r.is_null() for r in unwatched_rs ])) +@pytest.mark.skipif(not pytest.config.getoption("--port"), + reason="needs --port= to run") def test_async_unwatch_callback(async): a_rs = [] From 449099b2793a8d8cb01389f84828f068c10bb11c Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Sun, 3 Jul 2016 17:21:02 -0400 Subject: [PATCH 055/128] removed unused test file --- tests/test_elm327.py | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 tests/test_elm327.py diff --git a/tests/test_elm327.py b/tests/test_elm327.py deleted file mode 100644 index 781befc6..00000000 --- a/tests/test_elm327.py +++ /dev/null @@ -1,4 +0,0 @@ - -from obd.protocols import ECU, SAE_J1850_PWM -from obd.elm327 import ELM327 - From a4752be86ddb37e0d4fef800ff362fc5772f15c8 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Sun, 3 Jul 2016 17:31:16 -0400 Subject: [PATCH 056/128] updated docs on custom obd commands --- docs/Custom Commands.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/Custom Commands.md b/docs/Custom Commands.md index 150cb412..1b80fd74 100644 --- a/docs/Custom Commands.md +++ b/docs/Custom Commands.md @@ -5,8 +5,8 @@ If the command you need is not in python-OBDs tables, you can create a new `OBDC |----------------------|----------|----------------------------------------------------------------------------| | name | string | (human readability only) | | desc | string | (human readability only) | -| command | string | OBD command in hex (typically mode + PID | -| bytes | int | Number of bytes expected in response | +| command | bytes | OBD command in hex (typically mode + PID | +| bytes | int | Number of bytes expected in response (zero means unknown) | | decoder | callable | Function used for decoding messages from the OBD adapter | | ecu (optional) | ECU | ID of the ECU this command should listen to (`ECU.ALL` by default) | | fast (optional) | bool | Allows python-OBD to alter this command for efficieny (`False` by default) | @@ -21,13 +21,14 @@ from obd.protocols import ECU from obd.utils import bytes_to_int def rpm(messages): + """ decoder for RPM messages """ d = messages[0].data v = bytes_to_int(d) / 4.0 # helper function for converting byte arrays to ints - return (v, Unit.RPM) + return v * Unit.RPM # construct a Pint Quantity c = OBDCommand("RPM", \ # name "Engine RPM", \ # description - "010C", \ # command + b"010C", \ # command 2, \ # number of return bytes to expect rpm, \ # decoding function ECU.ENGINE, \ # (optional) ECU filter @@ -64,10 +65,10 @@ The `decoder` argument is a function of following form. ```python def (): ... - return (, ) + return ``` -Decoders are given a list of `Message` objects as an argument. If your decoder is called, this list is garaunteed to have at least one message object. Each `Message` object has a `data` property, which holds a parsed byte array, and is also garauteed to have the number of bytes specified by the command. +The return value of your decoder will be loaded into the `OBDResponse.value` field. Decoders are given a list of `Message` objects as an argument. If your decoder is called, this list is garaunteed to have at least one message object. Each `Message` object has a `data` property, which holds a parsed bytearray, and is also garauteed to have the number of bytes specified by the command. *NOTE: If you are transitioning from an older version of Python-OBD (where decoders were given raw hex strings as arguments), you can use the `Message.hex()` function as a patch.* @@ -75,7 +76,7 @@ Decoders are given a list of `Message` objects as an argument. If your decoder i def (messages): _hex = messages[0].hex() ... - return (, ) + return ``` *You can also access the original string sent by the adapter using the `Message.raw()` function.* From 10b660f0140c422f5eac82135d0f22705d490aea Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Sun, 3 Jul 2016 17:33:34 -0400 Subject: [PATCH 057/128] condensed custom commands unsupported handling --- docs/Custom Commands.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/Custom Commands.md b/docs/Custom Commands.md index 1b80fd74..cef34172 100644 --- a/docs/Custom Commands.md +++ b/docs/Custom Commands.md @@ -38,16 +38,14 @@ c = OBDCommand("RPM", \ # name By default, custom commands will be treated as "unsupported by the vehicle". There are two ways to handle this: ```python -# use the `force` parameter when querying o = obd.OBD() + +# use the `force` parameter when querying o.query(c, force=True) -``` -or +# OR -```python # add your command to the set of supported commands -o = obd.OBD() o.supported_commands.add(c) o.query(c) ``` From 4a1e9ee0eec4129a88431280e244d065366f3fcc Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Sun, 3 Jul 2016 17:36:13 -0400 Subject: [PATCH 058/128] simplified debug enable into a oneliner --- docs/Debug.md | 3 +-- docs/Troubleshooting.md | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/Debug.md b/docs/Debug.md index ea7fd9dc..0f929826 100644 --- a/docs/Debug.md +++ b/docs/Debug.md @@ -2,9 +2,8 @@ python-OBD uses python's builtin logging system. By default, it is setup to send ```python import obd -import logging -obd.logger.setLevel(logging.DEBUG) +obd.logger.setLevel(obd.logging.DEBUG) # enables all debug information ``` Or, to silence all logging output from python-OBD: diff --git a/docs/Troubleshooting.md b/docs/Troubleshooting.md index 317793ac..f2b9baca 100644 --- a/docs/Troubleshooting.md +++ b/docs/Troubleshooting.md @@ -4,8 +4,7 @@ If python-OBD is not working properly, the first thing you should do is enable debug output. Add the following line before your connection code to print all of the debug information to your console: ```python -import logging -obd.logger.setLevel(logging.DEBUG) +obd.logger.setLevel(obd.logging.DEBUG) ``` Here are some common logs from python-OBD, and their meanings: From dc157a39711e3df7d472ee053aa8d6b16a48202b Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Sun, 3 Jul 2016 18:25:06 -0400 Subject: [PATCH 059/128] tweaked serial code for modern pyserial API --- obd/elm327.py | 56 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/obd/elm327.py b/obd/elm327.py index 20c224fb..14579d00 100644 --- a/obd/elm327.py +++ b/obd/elm327.py @@ -114,11 +114,10 @@ def __init__(self, portname, baudrate, protocol): try: logger.info("Opening serial port '%s'" % portname) self.__port = serial.Serial(portname, \ - baudrate = baudrate, \ parity = serial.PARITY_NONE, \ stopbits = 1, \ - bytesize = 8, \ - timeout = 3) # seconds + bytesize = 8, + timeout = 3) # seconds logger.info("Serial port successfully opened on " + self.port_name()) except serial.SerialException as e: @@ -128,6 +127,10 @@ def __init__(self, portname, baudrate, protocol): self.__error(e) return + # ------------------------ find the ELM's baud ------------------------ + + if not self.set_baudrate(baudrate): + self.__error("Failed to set baudrate"); # ---------------------------- ATZ (reset) ---------------------------- try: @@ -159,14 +162,14 @@ def __init__(self, portname, baudrate, protocol): self.__status = OBDStatus.ELM_CONNECTED # try to communicate with the car, and load the correct protocol parser - if self.load_protocol(protocol): + if self.set_protocol(protocol): self.__status = OBDStatus.CAR_CONNECTED logger.info("Connection successful") else: logger.error("Connected to the adapter, but failed to connect to the vehicle") - def load_protocol(self, protocol): + def set_protocol(self, protocol): if protocol is not None: # an explicit protocol was specified if protocol not in self._SUPPORTED_PROTOCOLS: @@ -241,33 +244,44 @@ def auto_protocol(self): return False - def auto_baudrate(self): - """Detect, select, and return the baud rate at which a connected - ELM32x interface is operating. + def set_baudrate(self, baud): + if baud is None: + return self.auto_baudrate() + else: + self.__port.baudrate = baud + return True - Return None if the baud rate couldn't be determined. + + def auto_baudrate(self): + """ + Detect the baud rate at which a connected ELM32x interface is operating. + Returns boolean for success. """ + + # before we change the timout, save the "normal" value + timeout = self.__port.timeout + self.__port.timeout = 0.05 # we're only talking with the ELM, so things should go quickly + for baud in self._TRY_BAUDS: - self.port.setBaudrate(baud) - self.port.flushInput() - self.port.flushOutput() + self.__port.baudrate = baud + self.__port.flushInput() + self.__port.flushOutput() # Send a nonsense command to get a prompt back from the scanner # (an empty command runs the risk of repeating a dangerous command) # The first character might get eaten if the interface was busy, # so write a second one (again so that the lone CR doesn't repeat # the previous command) - port.write("\x7F\x7F\r") - port.set_timeout(timeout) - response = self.__read() + self.__port.write("\x7F\x7F\r\n") + response = self.port.read(1024) - if (response.endswith("\r\r>")): - #print "%d baud detected (%r)" % (baud, response) - break - else: - baud = None + # watch for the prompt character + if response.endswith(b">"): + return True + + self.__port.timeout = timeout - return baud + return False From 0b5ad60ae5557c3a2a8eb6ff57d757b45a881513 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Sun, 3 Jul 2016 20:49:51 -0400 Subject: [PATCH 060/128] added debug statements, increased timeout --- obd/elm327.py | 13 ++++++++++--- obd/obd.py | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/obd/elm327.py b/obd/elm327.py index 14579d00..4a17eb63 100644 --- a/obd/elm327.py +++ b/obd/elm327.py @@ -241,6 +241,7 @@ def auto_protocol(self): return True # if we've come this far, then we have failed... + logger.error("Failed to determine protocol") return False @@ -258,9 +259,11 @@ def auto_baudrate(self): Returns boolean for success. """ + logger.debug("Choosing baudrate automatically") + # before we change the timout, save the "normal" value timeout = self.__port.timeout - self.__port.timeout = 0.05 # we're only talking with the ELM, so things should go quickly + self.__port.timeout = 0.3 # we're only talking with the ELM, so things should go quickly for baud in self._TRY_BAUDS: self.__port.baudrate = baud @@ -272,15 +275,19 @@ def auto_baudrate(self): # The first character might get eaten if the interface was busy, # so write a second one (again so that the lone CR doesn't repeat # the previous command) - self.__port.write("\x7F\x7F\r\n") - response = self.port.read(1024) + self.__port.write(b"\x7F\x7F\r\n") + self.__port.flush() + response = self.__port.read(1024) + logger.debug("Response from baud %d: %s" % (baud, repr(response))) # watch for the prompt character if response.endswith(b">"): + logger.debug("Choosing baud %d" % baud) return True self.__port.timeout = timeout + logger.debug("Failed to choose baud") return False diff --git a/obd/obd.py b/obd/obd.py index 8efbb52b..755de20a 100644 --- a/obd/obd.py +++ b/obd/obd.py @@ -47,7 +47,7 @@ class OBD(object): with it's assorted commands/sensors. """ - def __init__(self, portstr=None, baudrate=38400, protocol=None, fast=True): + def __init__(self, portstr=None, baudrate=None, protocol=None, fast=True): self.port = None self.supported_commands = set(commands.base_commands()) self.fast = fast From 594f545be8d7db9ad55f7e9bb7b7779620497bad Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Sun, 3 Jul 2016 21:24:29 -0400 Subject: [PATCH 061/128] don't bother with auto-baud when connecting to a pts --- obd/elm327.py | 11 ++++++++--- tests/test_obdsim.py | 6 ++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/obd/elm327.py b/obd/elm327.py index 4a17eb63..545c9939 100644 --- a/obd/elm327.py +++ b/obd/elm327.py @@ -247,7 +247,11 @@ def auto_protocol(self): def set_baudrate(self, baud): if baud is None: - return self.auto_baudrate() + # when connecting to pseudo terminal, don't bother with auto baud + if self.port_name().startswith("/dev/pts"): + return True + else: + return self.auto_baudrate() else: self.__port.baudrate = baud return True @@ -263,7 +267,7 @@ def auto_baudrate(self): # before we change the timout, save the "normal" value timeout = self.__port.timeout - self.__port.timeout = 0.3 # we're only talking with the ELM, so things should go quickly + self.__port.timeout = 0.1 # we're only talking with the ELM, so things should go quickly for baud in self._TRY_BAUDS: self.__port.baudrate = baud @@ -283,11 +287,12 @@ def auto_baudrate(self): # watch for the prompt character if response.endswith(b">"): logger.debug("Choosing baud %d" % baud) + self.__port.timeout = timeout # reinstate our original timeout return True - self.__port.timeout = timeout logger.debug("Failed to choose baud") + self.__port.timeout = timeout # reinstate our original timeout return False diff --git a/tests/test_obdsim.py b/tests/test_obdsim.py index 3dc46fb4..d94862dc 100644 --- a/tests/test_obdsim.py +++ b/tests/test_obdsim.py @@ -3,7 +3,7 @@ import pytest from obd import commands, Unit -STANDARD_WAIT_TIME = 0.1 +STANDARD_WAIT_TIME = 0.2 @pytest.fixture(scope="module") @@ -23,7 +23,9 @@ def async(request): def good_rpm_response(r): - return (r.value.u == Unit.rpm) and (r.value >= 0.0 * Unit.rpm) + return (not r.is_null()) and \ + (r.value.u == Unit.rpm) and \ + (r.value >= 0.0 * Unit.rpm) @pytest.mark.skipif(not pytest.config.getoption("--port"), From b8535d47f357e7120eb80a7f9207950b6c70cc7e Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Sun, 3 Jul 2016 22:32:59 -0400 Subject: [PATCH 062/128] set async to auto-baud by default --- obd/async.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obd/async.py b/obd/async.py index 9ea67b40..d64ec7c1 100644 --- a/obd/async.py +++ b/obd/async.py @@ -44,7 +44,7 @@ class Async(OBD): Specialized for asynchronous value reporting. """ - def __init__(self, portstr=None, baudrate=38400, protocol=None, fast=True): + def __init__(self, portstr=None, baudrate=None, protocol=None, fast=True): super(Async, self).__init__(portstr, baudrate, protocol, fast) self.__commands = {} # key = OBDCommand, value = Response self.__callbacks = {} # key = OBDCommand, value = list of Functions From eec3bb5a7a8a210150144bdbe207a4cf4425442a Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Sun, 3 Jul 2016 22:49:06 -0400 Subject: [PATCH 063/128] removed commands that were deprecated in v0.5.0 --- docs/Connections.md | 6 ------ obd/OBDCommand.py | 5 ----- obd/__init__.py | 2 +- obd/obd.py | 6 ------ obd/utils.py | 5 ----- 5 files changed, 1 insertion(+), 23 deletions(-) diff --git a/docs/Connections.md b/docs/Connections.md index 36965ab6..749e05ea 100644 --- a/docs/Connections.md +++ b/docs/Connections.md @@ -91,12 +91,6 @@ Returns the string name for the currently connected port (`"/dev/ttyUSB0"`). If --- -### get_port_name() - -**Deprecated:** use `port_name()` instead - ---- - ### supports(command) Returns a boolean for whether a command is supported by both the car and python-OBD diff --git a/obd/OBDCommand.py b/obd/OBDCommand.py index 3b064856..2a3f2e09 100644 --- a/obd/OBDCommand.py +++ b/obd/OBDCommand.py @@ -78,11 +78,6 @@ def pid(self): else: return 0 - # TODO: remove later - @property - def supported(self): - logger.warning("OBDCommand.supported is deprecated. Use OBD.supports() instead") - return False def __call__(self, messages): diff --git a/obd/__init__.py b/obd/__init__.py index d95f5815..1a5c9d1b 100644 --- a/obd/__init__.py +++ b/obd/__init__.py @@ -43,7 +43,7 @@ from .OBDCommand import OBDCommand from .OBDResponse import OBDResponse from .protocols import ECU -from .utils import scan_serial, scanSerial, OBDStatus # TODO: scanSerial() deprecated +from .utils import scan_serial, OBDStatus from .UnitsAndScaling import Unit import logging diff --git a/obd/obd.py b/obd/obd.py index 755de20a..d3aad0b7 100644 --- a/obd/obd.py +++ b/obd/obd.py @@ -180,12 +180,6 @@ def protocol_id(self): return self.port.protocol_id() - def get_port_name(self): - # TODO: deprecated, remove later - logger.warning("OBD.get_port_name() is deprecated, use OBD.port_name() instead") - return self.port_name() - - def port_name(self): """ Returns the name of the currently connected port """ if self.port is not None: diff --git a/obd/utils.py b/obd/utils.py index 08ed6900..942c2fdc 100644 --- a/obd/utils.py +++ b/obd/utils.py @@ -151,8 +151,3 @@ def scan_serial(): available.append(port) return available - -# TODO: deprecated, remove later -def scanSerial(): - logger.warning("scanSerial() is deprecated, use scan_serial() instead") - return scan_serial() From 2f360d5c8cc62dc71bbe82d8bea65c8eb564e7ce Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Mon, 4 Jul 2016 14:31:25 -0400 Subject: [PATCH 064/128] added command validation function, check for mode 06 over non-CAN --- obd/async.py | 4 ++-- obd/obd.py | 23 ++++++++++++++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/obd/async.py b/obd/async.py index d64ec7c1..ecd43746 100644 --- a/obd/async.py +++ b/obd/async.py @@ -136,8 +136,8 @@ def watch(self, c, callback=None, force=False): logger.warning("Can't watch() while running, please use stop()") else: - if not force and not self.supports(c): - logger.warning("'%s' is not supported" % str(c)) + if not force and not self.test_cmd(c): + # self.test_cmd() will print warnings return # new command being watched, store the command diff --git a/obd/obd.py b/obd/obd.py index d3aad0b7..9d91a6bb 100644 --- a/obd/obd.py +++ b/obd/obd.py @@ -215,6 +215,24 @@ def supports(self, cmd): return cmd in self.supported_commands + def test_cmd(self, cmd): + """ + Returns a boolean for whether a command will + be sent without using force=True. + """ + # test if the command is supported + if not self.supports(cmd): + logger.warning("'%s' is not supported" % str(cmd)) + return False + + # mode 06 is only implemented for the CAN protocols + if cmd.mode == 6 and self.port.protocol_id() not in ["6", "7", "8", "9"]: + logger.warning("Mode 06 commands are only supported over CAN protocols") + return False + + return True + + def query(self, cmd, force=False): """ primary API function. Sends commands to the car, and @@ -225,11 +243,10 @@ def query(self, cmd, force=False): logger.warning("Query failed, no connection available") return OBDResponse() - if not force and not self.supports(cmd): - logger.warning("'%s' is not supported" % str(cmd)) + # if the user forces, skip all checks + if not force and not self.test_cmd(cmd): return OBDResponse() - # send command and retrieve message logger.info("Sending command: %s" % str(cmd)) cmd_string = self.__build_command_string(cmd) From 52e33a9e7bbbbb3bad1217b7f3484c83d37808cd Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Mon, 4 Jul 2016 14:34:11 -0400 Subject: [PATCH 065/128] fixed syntax error retrieving port status --- obd/obd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/obd/obd.py b/obd/obd.py index 9d91a6bb..30436d2f 100644 --- a/obd/obd.py +++ b/obd/obd.py @@ -84,7 +84,7 @@ def __connect(self, portstr, baudrate, protocol): self.port = ELM327(portstr, baudrate, protocol) # if the connection failed, close it - if self.port.status == OBDStatus.NOT_CONNECTED: + if self.port.status() == OBDStatus.NOT_CONNECTED: # the ELM327 class will report its own errors self.close() @@ -138,7 +138,7 @@ def close(self): Closes the connection, and clears supported_commands """ - self.supported_commands = [] + self.supported_commands = set() if self.port is not None: logger.info("Closing connection") From 73b1d2f9488e80950dc0000cd16f04845a27a3d7 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Mon, 4 Jul 2016 14:37:34 -0400 Subject: [PATCH 066/128] return empty strings instead of sentences --- obd/elm327.py | 3 ++- obd/obd.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/obd/elm327.py b/obd/elm327.py index 545c9939..343420d3 100644 --- a/obd/elm327.py +++ b/obd/elm327.py @@ -329,7 +329,7 @@ def port_name(self): if self.__port is not None: return self.__port.portstr else: - return "No Port" + return "" def status(self): @@ -433,6 +433,7 @@ def __read(self): buffer = bytearray() while True: + # retrieve as much data as possible data = self.__port.read(self.__port.in_waiting or 1) # if nothing was recieved diff --git a/obd/obd.py b/obd/obd.py index 30436d2f..7d825e15 100644 --- a/obd/obd.py +++ b/obd/obd.py @@ -185,7 +185,7 @@ def port_name(self): if self.port is not None: return self.port.port_name() else: - return "Not connected to any port" + return "" def is_connected(self): From 46cf2a551a27d9c9765d8a34623dac7638cc7591 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Mon, 4 Jul 2016 14:43:35 -0400 Subject: [PATCH 067/128] renamed OBD.port --> OBD.interface --- obd/obd.py | 42 +++++++++++++++++++++--------------------- tests/test_OBD.py | 44 ++++++++++++++++++++++---------------------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/obd/obd.py b/obd/obd.py index 7d825e15..97504ebc 100644 --- a/obd/obd.py +++ b/obd/obd.py @@ -48,7 +48,7 @@ class OBD(object): """ def __init__(self, portstr=None, baudrate=None, protocol=None, fast=True): - self.port = None + self.interface = None self.supported_commands = set(commands.base_commands()) self.fast = fast self.__last_command = "" # used for running the previous command with a CR @@ -75,16 +75,16 @@ def __connect(self, portstr, baudrate, protocol): for port in portnames: logger.info("Attempting to use port: " + str(port)) - self.port = ELM327(port, baudrate, protocol) + self.interface = ELM327(port, baudrate, protocol) - if self.port.status() >= OBDStatus.ELM_CONNECTED: + if self.interface.status() >= OBDStatus.ELM_CONNECTED: break # success! stop searching for serial else: logger.info("Explicit port defined") - self.port = ELM327(portstr, baudrate, protocol) + self.interface = ELM327(portstr, baudrate, protocol) # if the connection failed, close it - if self.port.status() == OBDStatus.NOT_CONNECTED: + if self.interface.status() == OBDStatus.NOT_CONNECTED: # the ELM327 class will report its own errors self.close() @@ -140,50 +140,50 @@ def close(self): self.supported_commands = set() - if self.port is not None: + if self.interface is not None: logger.info("Closing connection") - self.port.close() - self.port = None + self.interface.close() + self.interface = None def status(self): """ returns the OBD connection status """ - if self.port is None: + if self.interface is None: return OBDStatus.NOT_CONNECTED else: - return self.port.status() + return self.interface.status() # not sure how useful this would be # def ecus(self): # """ returns a list of ECUs in the vehicle """ - # if self.port is None: + # if self.interface is None: # return [] # else: - # return self.port.ecus() + # return self.interface.ecus() def protocol_name(self): """ returns the name of the protocol being used by the ELM327 """ - if self.port is None: + if self.interface is None: return "" else: - return self.port.protocol_name() + return self.interface.protocol_name() def protocol_id(self): """ returns the ID of the protocol being used by the ELM327 """ - if self.port is None: + if self.interface is None: return "" else: - return self.port.protocol_id() + return self.interface.protocol_id() def port_name(self): """ Returns the name of the currently connected port """ - if self.port is not None: - return self.port.port_name() + if self.interface is not None: + return self.interface.port_name() else: return "" @@ -226,7 +226,7 @@ def test_cmd(self, cmd): return False # mode 06 is only implemented for the CAN protocols - if cmd.mode == 6 and self.port.protocol_id() not in ["6", "7", "8", "9"]: + if cmd.mode == 6 and self.interface.protocol_id() not in ["6", "7", "8", "9"]: logger.warning("Mode 06 commands are only supported over CAN protocols") return False @@ -250,7 +250,7 @@ def query(self, cmd, force=False): # send command and retrieve message logger.info("Sending command: %s" % str(cmd)) cmd_string = self.__build_command_string(cmd) - messages = self.port.send_and_parse(cmd_string) + messages = self.interface.send_and_parse(cmd_string) # if we're sending a new command, note it if cmd_string: @@ -269,7 +269,7 @@ def __build_command_string(self, cmd): # only wait for as many ECUs as we've seen if self.fast and cmd.fast: - cmd_string += str(len(self.port.ecus())).encode() + cmd_string += str(len(self.interface.ecus())).encode() # if we sent this last time, just send if self.fast and (cmd_string == self.__last_command): diff --git a/tests/test_OBD.py b/tests/test_OBD.py index 35b22b81..81516f2f 100644 --- a/tests/test_OBD.py +++ b/tests/test_OBD.py @@ -76,7 +76,7 @@ def test_is_connected(): assert not o.is_connected() # our fake ELM class always returns success for connections - o.port = FakeELM("/dev/null") + o.interface = FakeELM("/dev/null") assert o.is_connected() @@ -88,17 +88,17 @@ def test_status(): o = obd.OBD("/dev/null") assert o.status() == OBDStatus.NOT_CONNECTED - o.port = None + o.interface = None assert o.status() == OBDStatus.NOT_CONNECTED # we can manually set our fake ELM class to test # the other values - o.port = FakeELM("/dev/null") + o.interface = FakeELM("/dev/null") - o.port._status = OBDStatus.ELM_CONNECTED + o.interface._status = OBDStatus.ELM_CONNECTED assert o.status() == OBDStatus.ELM_CONNECTED - o.port._status = OBDStatus.CAR_CONNECTED + o.interface._status = OBDStatus.CAR_CONNECTED assert o.status() == OBDStatus.CAR_CONNECTED @@ -121,31 +121,31 @@ def test_port_name(): same values as the underlying ELM327 class. """ o = obd.OBD("/dev/null") - o.port = FakeELM("/dev/null") - assert o.port_name() == o.port._portname + o.interface = FakeELM("/dev/null") + assert o.port_name() == o.interface._portname - o.port = FakeELM("A different port name") - assert o.port_name() == o.port._portname + o.interface = FakeELM("A different port name") + assert o.port_name() == o.interface._portname def test_protocol_name(): o = obd.OBD("/dev/null") - o.port = None + o.interface = None assert o.protocol_name() == "" - o.port = FakeELM("/dev/null") - assert o.protocol_name() == o.port.protocol_name() + o.interface = FakeELM("/dev/null") + assert o.protocol_name() == o.interface.protocol_name() def test_protocol_id(): o = obd.OBD("/dev/null") - o.port = None + o.interface = None assert o.protocol_id() == "" - o.port = FakeELM("/dev/null") - assert o.protocol_id() == o.port.protocol_id() + o.interface = FakeELM("/dev/null") + assert o.protocol_id() == o.interface.protocol_id() @@ -158,30 +158,30 @@ def test_protocol_id(): def test_force(): o = obd.OBD("/dev/null", fast=False) # disable the trailing response count byte - o.port = FakeELM("/dev/null") + o.interface = FakeELM("/dev/null") r = o.query(obd.commands.RPM) assert r.is_null() - assert o.port._test_last_command(None) + assert o.interface._test_last_command(None) r = o.query(obd.commands.RPM, force=True) assert not r.is_null() - assert o.port._test_last_command(obd.commands.RPM.command) + assert o.interface._test_last_command(obd.commands.RPM.command) # a command that isn't in python-OBD's tables r = o.query(command) assert r.is_null() - assert o.port._test_last_command(None) + assert o.interface._test_last_command(None) r = o.query(command, force=True) - assert o.port._test_last_command(command.command) + assert o.interface._test_last_command(command.command) def test_fast(): o = obd.OBD("/dev/null", fast=False) - o.port = FakeELM("/dev/null") + o.interface = FakeELM("/dev/null") assert command.fast o.query(command, force=True) # force since this command isn't in the tables - # assert o.port._test_last_command(command.command) + # assert o.interface._test_last_command(command.command) From 178e1a59318c9336377060728c0bc629ff0a2986 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Mon, 4 Jul 2016 19:03:12 -0400 Subject: [PATCH 068/128] added tests for unsigned units --- obd/UnitsAndScaling.py | 2 +- tests/test_uas.py | 375 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 376 insertions(+), 1 deletion(-) create mode 100644 tests/test_uas.py diff --git a/obd/UnitsAndScaling.py b/obd/UnitsAndScaling.py index f36fccb7..4fbe9f1e 100644 --- a/obd/UnitsAndScaling.py +++ b/obd/UnitsAndScaling.py @@ -133,7 +133,7 @@ def __call__(self, _bytes): 0x3E : UAS(False, 0.00006103516, Unit.millimeter ** 2), 0x3F : UAS(False, 0.01, Unit.liter), 0x40 : UAS(False, 1, Unit.ppm), - 0x41 : UAS(False, 0.1, Unit.microampere), + 0x41 : UAS(False, 0.01, Unit.microampere), # signed ----------------------------------------- 0x81 : UAS(True, 1, Unit.count), diff --git a/tests/test_uas.py b/tests/test_uas.py new file mode 100644 index 00000000..98fedbb3 --- /dev/null +++ b/tests/test_uas.py @@ -0,0 +1,375 @@ + +from binascii import unhexlify +from obd.UnitsAndScaling import Unit, UAS_IDS + + +# shim to convert human-readable hex into bytearray +def b(_hex): + return bytearray(unhexlify(_hex)) + +FLOAT_EQUALS_TOLERANCE = 0.025 + +# comparison for pint floating point values +def float_equals(va, vb): + units_match = (va.u == vb.u) + values_match = (abs(va.magnitude - vb.magnitude) < FLOAT_EQUALS_TOLERANCE) + return values_match and units_match + + +""" +Unsigned Units +""" + +def test_01(): + assert UAS_IDS[0x01](b("0000")) == 0 * Unit.count + assert UAS_IDS[0x01](b("0001")) == 1 * Unit.count + assert UAS_IDS[0x01](b("FFFF")) == 65535 * Unit.count + +def test_02(): + assert UAS_IDS[0x02](b("0000")) == 0 * Unit.count + assert UAS_IDS[0x02](b("0001")) == 0.1 * Unit.count + assert UAS_IDS[0x02](b("FFFF")) == 6553.5 * Unit.count + +def test_03(): + assert UAS_IDS[0x03](b("0000")) == 0 * Unit.count + assert UAS_IDS[0x03](b("0001")) == 0.01 * Unit.count + assert UAS_IDS[0x03](b("FFFF")) == 655.35 * Unit.count + +def test_04(): + assert UAS_IDS[0x04](b("0000")) == 0 * Unit.count + assert UAS_IDS[0x04](b("0001")) == 0.001 * Unit.count + assert UAS_IDS[0x04](b("FFFF")) == 65.535 * Unit.count + +def test_05(): + assert float_equals(UAS_IDS[0x05](b("0000")), 0 * Unit.count) + assert float_equals(UAS_IDS[0x05](b("0001")), 0.0000305 * Unit.count) + assert float_equals(UAS_IDS[0x05](b("FFFF")), 1.9999 * Unit.count) + +def test_06(): + assert float_equals(UAS_IDS[0x06](b("0000")), 0 * Unit.count) + assert float_equals(UAS_IDS[0x06](b("0001")), 0.000305 * Unit.count) + assert float_equals(UAS_IDS[0x06](b("FFFF")), 19.988 * Unit.count) + +def test_07(): + assert float_equals(UAS_IDS[0x07](b("0000")), 0 * Unit.rpm) + assert float_equals(UAS_IDS[0x07](b("0002")), 0.5 * Unit.rpm) + assert float_equals(UAS_IDS[0x07](b("FFFD")), 16383.25 * Unit.rpm) + assert float_equals(UAS_IDS[0x07](b("FFFF")), 16383.75 * Unit.rpm) + +def test_08(): + assert float_equals(UAS_IDS[0x08](b("0000")), 0 * Unit.kph) + assert float_equals(UAS_IDS[0x08](b("0064")), 1 * Unit.kph) + assert float_equals(UAS_IDS[0x08](b("03E7")), 9.99 * Unit.kph) + assert float_equals(UAS_IDS[0x08](b("FFFF")), 655.35 * Unit.kph) + +def test_09(): + assert float_equals(UAS_IDS[0x09](b("0000")), 0 * Unit.kph) + assert float_equals(UAS_IDS[0x09](b("0064")), 100 * Unit.kph) + assert float_equals(UAS_IDS[0x09](b("03E7")), 999 * Unit.kph) + assert float_equals(UAS_IDS[0x09](b("FFFF")), 65535 * Unit.kph) + +def test_0A(): + # the standard gives example values that don't line up perfectly + # with the scale. The last two tests here deviate from the standard + assert float_equals(UAS_IDS[0x0A](b("0000")), 0 * Unit.millivolt) + assert float_equals(UAS_IDS[0x0A](b("0001")), 0.122 * Unit.millivolt) + assert float_equals(UAS_IDS[0x0A](b("2004")), 999.912 * Unit.millivolt) # 1000.488 mV + assert float_equals(UAS_IDS[0x0A](b("FFFF")), 7995.27 * Unit.millivolt) # 7999 mV + +def test_0B(): + assert UAS_IDS[0x0B](b("0000")) == 0 * Unit.volt + assert UAS_IDS[0x0B](b("0001")) == 0.001 * Unit.volt + assert UAS_IDS[0x0B](b("FFFF")) == 65.535 * Unit.volt + +def test_0C(): + assert float_equals(UAS_IDS[0x0C](b("0000")), 0 * Unit.volt) + assert float_equals(UAS_IDS[0x0C](b("0001")), 0.01 * Unit.volt) + assert float_equals(UAS_IDS[0x0C](b("FFFF")), 655.350 * Unit.volt) + +def test_0D(): + assert float_equals(UAS_IDS[0x0D](b("0000")), 0 * Unit.milliampere) + assert float_equals(UAS_IDS[0x0D](b("0001")), 0.004 * Unit.milliampere) + assert float_equals(UAS_IDS[0x0D](b("8000")), 128 * Unit.milliampere) + assert float_equals(UAS_IDS[0x0D](b("FFFF")), 255.996 * Unit.milliampere) + +def test_0E(): + assert UAS_IDS[0x0E](b("0000")) == 0 * Unit.ampere + assert UAS_IDS[0x0E](b("8000")) == 32.768 * Unit.ampere + assert UAS_IDS[0x0E](b("FFFF")) == 65.535 * Unit.ampere + +def test_0F(): + assert UAS_IDS[0x0F](b("0000")) == 0 * Unit.ampere + assert UAS_IDS[0x0F](b("0001")) == 0.01 * Unit.ampere + assert UAS_IDS[0x0F](b("FFFF")) == 655.35 * Unit.ampere + +def test_10(): + assert UAS_IDS[0x10](b("0000")) == 0 * Unit.millisecond + assert UAS_IDS[0x10](b("8000")) == 32768 * Unit.millisecond + assert UAS_IDS[0x10](b("EA60")) == 60000 * Unit.millisecond + assert UAS_IDS[0x10](b("FFFF")) == 65535 * Unit.millisecond + +def test_11(): + assert UAS_IDS[0x11](b("0000")) == 0 * Unit.millisecond + assert UAS_IDS[0x11](b("8000")) == 3276800 * Unit.millisecond + assert UAS_IDS[0x11](b("EA60")) == 6000000 * Unit.millisecond + assert UAS_IDS[0x11](b("FFFF")) == 6553500 * Unit.millisecond + +def test_12(): + assert UAS_IDS[0x12](b("0000")) == 0 * Unit.second + assert UAS_IDS[0x12](b("003C")) == 60 * Unit.second + assert UAS_IDS[0x12](b("0E10")) == 3600 * Unit.second + assert UAS_IDS[0x12](b("FFFF")) == 65535 * Unit.second + +def test_13(): + assert UAS_IDS[0x13](b("0000")) == 0 * Unit.milliohm + assert UAS_IDS[0x13](b("0001")) == 1 * Unit.milliohm + assert UAS_IDS[0x13](b("8000")) == 32768 * Unit.milliohm + assert UAS_IDS[0x13](b("FFFF")) == 65535 * Unit.milliohm + +def test_14(): + assert UAS_IDS[0x14](b("0000")) == 0 * Unit.ohm + assert UAS_IDS[0x14](b("0001")) == 1 * Unit.ohm + assert UAS_IDS[0x14](b("8000")) == 32768 * Unit.ohm + assert UAS_IDS[0x14](b("FFFF")) == 65535 * Unit.ohm + +def test_15(): + assert UAS_IDS[0x15](b("0000")) == 0 * Unit.kiloohm + assert UAS_IDS[0x15](b("0001")) == 1 * Unit.kiloohm + assert UAS_IDS[0x15](b("8000")) == 32768 * Unit.kiloohm + assert UAS_IDS[0x15](b("FFFF")) == 65535 * Unit.kiloohm + +def test_16(): + assert UAS_IDS[0x16](b("0000")) == Unit.Quantity(-40, Unit.celsius) + assert UAS_IDS[0x16](b("0001")) == Unit.Quantity(-39.9, Unit.celsius) + assert UAS_IDS[0x16](b("00DC")) == Unit.Quantity(-18, Unit.celsius) + assert UAS_IDS[0x16](b("0190")) == Unit.Quantity(0, Unit.celsius) + assert UAS_IDS[0x16](b("FFFF")) == Unit.Quantity(6513.5, Unit.celsius) + +def test_17(): + assert UAS_IDS[0x17](b("0000")) == 0 * Unit.kilopascal + assert UAS_IDS[0x17](b("0001")) == 0.01 * Unit.kilopascal + assert UAS_IDS[0x17](b("FFFF")) == 655.35 * Unit.kilopascal + +def test_18(): + assert UAS_IDS[0x18](b("0000")) == 0 * Unit.kilopascal + assert UAS_IDS[0x18](b("0001")) == 0.0117 * Unit.kilopascal + assert UAS_IDS[0x18](b("FFFF")) == 766.7595 * Unit.kilopascal + +def test_19(): + assert UAS_IDS[0x19](b("0000")) == 0 * Unit.kilopascal + assert UAS_IDS[0x19](b("0001")) == 0.079 * Unit.kilopascal + assert UAS_IDS[0x19](b("FFFF")) == 5177.265 * Unit.kilopascal + +def test_1A(): + assert UAS_IDS[0x1A](b("0000")) == 0 * Unit.kilopascal + assert UAS_IDS[0x1A](b("0001")) == 1 * Unit.kilopascal + assert UAS_IDS[0x1A](b("FFFF")) == 65535 * Unit.kilopascal + +def test_1B(): + assert UAS_IDS[0x1B](b("0000")) == 0 * Unit.kilopascal + assert UAS_IDS[0x1B](b("0001")) == 10 * Unit.kilopascal + assert UAS_IDS[0x1B](b("FFFF")) == 655350 * Unit.kilopascal + +def test_1C(): + assert UAS_IDS[0x1C](b("0000")) == 0 * Unit.degree + assert UAS_IDS[0x1C](b("0001")) == 0.01 * Unit.degree + assert UAS_IDS[0x1C](b("8CA0")) == 360 * Unit.degree + assert UAS_IDS[0x1C](b("FFFF")) == 655.35 * Unit.degree + +def test_1D(): + assert UAS_IDS[0x1D](b("0000")) == 0 * Unit.degree + assert UAS_IDS[0x1D](b("0001")) == 0.5 * Unit.degree + assert UAS_IDS[0x1D](b("FFFF")) == 32767.5 * Unit.degree + +def test_1E(): + assert float_equals(UAS_IDS[0x1E](b("0000")), 0 * Unit.ratio) + assert float_equals(UAS_IDS[0x1E](b("8013")), 1 * Unit.ratio) + assert float_equals(UAS_IDS[0x1E](b("FFFF")), 1.999 * Unit.ratio) + +def test_1F(): + assert float_equals(UAS_IDS[0x1F](b("0000")), 0 * Unit.ratio) + assert float_equals(UAS_IDS[0x1F](b("0001")), 0.05 * Unit.ratio) + assert float_equals(UAS_IDS[0x1F](b("0014")), 1 * Unit.ratio) + assert float_equals(UAS_IDS[0x1F](b("0126")), 14.7 * Unit.ratio) + assert float_equals(UAS_IDS[0x1F](b("FFFF")), 3276.75 * Unit.ratio) + +def test_20(): + assert float_equals(UAS_IDS[0x20](b("0000")), 0 * Unit.ratio) + assert float_equals(UAS_IDS[0x20](b("0001")), 0.0039062 * Unit.ratio) + assert float_equals(UAS_IDS[0x20](b("FFFF")), 255.993 * Unit.ratio) + +def test_21(): + assert UAS_IDS[0x21](b("0000")) == 0 * Unit.millihertz + assert UAS_IDS[0x21](b("8000")) == 32768 * Unit.millihertz + assert UAS_IDS[0x21](b("FFFF")) == 65535 * Unit.millihertz + +def test_22(): + assert UAS_IDS[0x22](b("0000")) == 0 * Unit.hertz + assert UAS_IDS[0x22](b("8000")) == 32768 * Unit.hertz + assert UAS_IDS[0x22](b("FFFF")) == 65535 * Unit.hertz + +def test_23(): + assert UAS_IDS[0x23](b("0000")) == 0 * Unit.kilohertz + assert UAS_IDS[0x23](b("8000")) == 32768 * Unit.kilohertz + assert UAS_IDS[0x23](b("FFFF")) == 65535 * Unit.kilohertz + +def test_24(): + assert UAS_IDS[0x24](b("0000")) == 0 * Unit.count + assert UAS_IDS[0x24](b("0001")) == 1 * Unit.count + assert UAS_IDS[0x24](b("FFFF")) == 65535 * Unit.count + +def test_25(): + assert UAS_IDS[0x25](b("0000")) == 0 * Unit.kilometer + assert UAS_IDS[0x25](b("0001")) == 1 * Unit.kilometer + assert UAS_IDS[0x25](b("FFFF")) == 65535 * Unit.kilometer + +def test_26(): + assert UAS_IDS[0x26](b("0000")) == 0 * Unit.millivolt / Unit.millisecond + assert UAS_IDS[0x26](b("0001")) == 0.1 * Unit.millivolt / Unit.millisecond + assert UAS_IDS[0x26](b("FFFF")) == 6553.5 * Unit.millivolt / Unit.millisecond + +def test_27(): + assert UAS_IDS[0x27](b("0000")) == 0 * Unit.grams_per_second + assert UAS_IDS[0x27](b("0001")) == 0.01 * Unit.grams_per_second + assert UAS_IDS[0x27](b("FFFF")) == 655.35 * Unit.grams_per_second + +def test_28(): + assert UAS_IDS[0x28](b("0000")) == 0 * Unit.grams_per_second + assert UAS_IDS[0x28](b("0001")) == 1 * Unit.grams_per_second + assert UAS_IDS[0x28](b("FFFF")) == 65535 * Unit.grams_per_second + +def test_29(): + assert UAS_IDS[0x29](b("0000")) == 0 * Unit.pascal / Unit.second + assert UAS_IDS[0x29](b("0004")) == 1 * Unit.pascal / Unit.second + assert UAS_IDS[0x29](b("FFFF")) == 16383.75 * Unit.pascal / Unit.second # deviates from standard examples + +def test_2A(): + assert UAS_IDS[0x2A](b("0000")) == 0 * Unit.kilogram / Unit.hour + assert UAS_IDS[0x2A](b("0001")) == 0.001 * Unit.kilogram / Unit.hour + assert UAS_IDS[0x2A](b("FFFF")) == 65.535 * Unit.kilogram / Unit.hour + +def test_2B(): + assert UAS_IDS[0x2B](b("0000")) == 0 * Unit.count + assert UAS_IDS[0x2B](b("0001")) == 1 * Unit.count + assert UAS_IDS[0x2B](b("FFFF")) == 65535 * Unit.count + +def test_2C(): + assert UAS_IDS[0x2C](b("0000")) == 0 * Unit.gram + assert UAS_IDS[0x2C](b("0001")) == 0.01 * Unit.gram + assert UAS_IDS[0x2C](b("FFFF")) == 655.35 * Unit.gram + +def test_2D(): + assert UAS_IDS[0x2D](b("0000")) == 0 * Unit.milligram + assert UAS_IDS[0x2D](b("0001")) == 0.01 * Unit.milligram + assert UAS_IDS[0x2D](b("FFFF")) == 655.35 * Unit.milligram + +def test_2E(): + assert UAS_IDS[0x2E](b("0000")) == False + assert UAS_IDS[0x2E](b("0001")) == True + assert UAS_IDS[0x2E](b("FFFF")) == True + +def test_2F(): + assert UAS_IDS[0x2F](b("0000")) == 0 * Unit.percent + assert UAS_IDS[0x2F](b("0001")) == 0.01 * Unit.percent + assert UAS_IDS[0x2F](b("2710")) == 100 * Unit.percent + assert UAS_IDS[0x2F](b("FFFF")) == 655.35 * Unit.percent + +def test_30(): + assert float_equals(UAS_IDS[0x30](b("0000")), 0 * Unit.percent) + assert float_equals(UAS_IDS[0x30](b("0001")), 0.001526 * Unit.percent) + assert float_equals(UAS_IDS[0x30](b("FFFF")), 100.00641 * Unit.percent) + +def test_31(): + assert UAS_IDS[0x31](b("0000")) == 0 * Unit.liter + assert UAS_IDS[0x31](b("0001")) == 0.001 * Unit.liter + assert UAS_IDS[0x31](b("FFFF")) == 65.535 * Unit.liter + +def test_32(): + assert float_equals(UAS_IDS[0x32](b("0000")), 0 * Unit.inch) + assert float_equals(UAS_IDS[0x32](b("0010")), 0.0004883 * Unit.inch) + assert float_equals(UAS_IDS[0x32](b("0011")), 0.0005188 * Unit.inch) + assert float_equals(UAS_IDS[0x32](b("FFFF")), 1.9999695 * Unit.inch) + +def test_33(): + assert float_equals(UAS_IDS[0x33](b("0000")), 0 * Unit.ratio) + assert float_equals(UAS_IDS[0x33](b("0001")), 0.00024414 * Unit.ratio) + assert float_equals(UAS_IDS[0x33](b("1000")), 1.0 * Unit.ratio) + assert float_equals(UAS_IDS[0x33](b("E5BE")), 14.36 * Unit.ratio) + assert float_equals(UAS_IDS[0x33](b("FFFF")), 16.0 * Unit.ratio) + +def test_34(): + assert UAS_IDS[0x34](b("0000")) == 0 * Unit.minute + assert UAS_IDS[0x34](b("003C")) == 60 * Unit.minute + assert UAS_IDS[0x34](b("0E10")) == 3600 * Unit.minute + assert UAS_IDS[0x34](b("FFFF")) == 65535 * Unit.minute + +def test_35(): + assert UAS_IDS[0x35](b("0000")) == 0 * Unit.millisecond + assert UAS_IDS[0x35](b("8000")) == 327680 * Unit.millisecond + assert UAS_IDS[0x35](b("EA60")) == 600000 * Unit.millisecond + assert UAS_IDS[0x35](b("FFFF")) == 655350 * Unit.millisecond + +def test_36(): + assert UAS_IDS[0x36](b("0000")) == 0 * Unit.gram + assert UAS_IDS[0x36](b("0001")) == 0.01 * Unit.gram + assert UAS_IDS[0x36](b("FFFF")) == 655.35 * Unit.gram + +def test_37(): + assert UAS_IDS[0x37](b("0000")) == 0 * Unit.gram + assert UAS_IDS[0x37](b("0001")) == 0.1 * Unit.gram + assert UAS_IDS[0x37](b("FFFF")) == 6553.5 * Unit.gram + +def test_38(): + assert UAS_IDS[0x38](b("0000")) == 0 * Unit.gram + assert UAS_IDS[0x38](b("0001")) == 1 * Unit.gram + assert UAS_IDS[0x38](b("FFFF")) == 65535 * Unit.gram + +def test_39(): + assert float_equals(UAS_IDS[0x39](b("0000")), -327.68 * Unit.percent) + assert float_equals(UAS_IDS[0x39](b("58F0")), -100 * Unit.percent) + assert float_equals(UAS_IDS[0x39](b("7FFF")), -0.01 * Unit.percent) + assert float_equals(UAS_IDS[0x39](b("8000")), 0 * Unit.percent) + assert float_equals(UAS_IDS[0x39](b("8001")), 0.01 * Unit.percent) + assert float_equals(UAS_IDS[0x39](b("A710")), 100 * Unit.percent) + assert float_equals(UAS_IDS[0x39](b("FFFF")), 327.67 * Unit.percent) + +def test_3A(): + assert UAS_IDS[0x3A](b("0000")) == 0 * Unit.gram + assert UAS_IDS[0x3A](b("0001")) == 0.001 * Unit.gram + assert UAS_IDS[0x3A](b("FFFF")) == 65.535 * Unit.gram + +def test_3B(): + assert float_equals(UAS_IDS[0x3B](b("0000")), 0 * Unit.gram) + assert float_equals(UAS_IDS[0x3B](b("0001")), 0.0001 * Unit.gram) + assert float_equals(UAS_IDS[0x3B](b("FFFF")), 6.5535 * Unit.gram) + +def test_3C(): + assert UAS_IDS[0x3C](b("0000")) == 0 * Unit.microsecond + assert UAS_IDS[0x3C](b("8000")) == 3276.8 * Unit.microsecond + assert UAS_IDS[0x3C](b("EA60")) == 6000.0 * Unit.microsecond + assert UAS_IDS[0x3C](b("FFFF")) == 6553.5 * Unit.microsecond + +def test_3D(): + assert UAS_IDS[0x3D](b("0000")) == 0 * Unit.milliampere + assert UAS_IDS[0x3D](b("0001")) == 0.01 * Unit.milliampere + assert UAS_IDS[0x3D](b("FFFF")) == 655.35 * Unit.milliampere + +def test_3E(): + assert float_equals(UAS_IDS[0x3E](b("0000")), 0 * Unit.millimeter ** 2) + assert float_equals(UAS_IDS[0x3E](b("8000")), 1.9999 * Unit.millimeter ** 2) + assert float_equals(UAS_IDS[0x3E](b("FFFF")), 3.9999 * Unit.millimeter ** 2) + +def test_3F(): + assert UAS_IDS[0x3F](b("0000")) == 0 * Unit.liter + assert UAS_IDS[0x3F](b("0001")) == 0.01 * Unit.liter + assert UAS_IDS[0x3F](b("FFFF")) == 655.35 * Unit.liter + +def test_40(): + assert UAS_IDS[0x40](b("0000")) == 0 * Unit.ppm + assert UAS_IDS[0x40](b("0001")) == 1 * Unit.ppm + assert UAS_IDS[0x40](b("FFFF")) == 65535 * Unit.ppm + +def test_41(): + assert UAS_IDS[0x41](b("0000")) == 0 * Unit.microampere + assert UAS_IDS[0x41](b("0001")) == 0.01 * Unit.microampere + assert UAS_IDS[0x41](b("FFFF")) == 655.35 * Unit.microampere From 6f7894d9cd690a91b94b9375d37de5248d294232 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Mon, 4 Jul 2016 20:50:09 -0400 Subject: [PATCH 069/128] added tests for signed units --- tests/test_uas.py | 198 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) diff --git a/tests/test_uas.py b/tests/test_uas.py index 98fedbb3..142f9426 100644 --- a/tests/test_uas.py +++ b/tests/test_uas.py @@ -373,3 +373,201 @@ def test_41(): assert UAS_IDS[0x41](b("0000")) == 0 * Unit.microampere assert UAS_IDS[0x41](b("0001")) == 0.01 * Unit.microampere assert UAS_IDS[0x41](b("FFFF")) == 655.35 * Unit.microampere + + + + +""" +signed Units +""" + +def test_81(): + assert UAS_IDS[0x81](b("8000")) == -32768 * Unit.count + assert UAS_IDS[0x81](b("FFFF")) == -1 * Unit.count + assert UAS_IDS[0x81](b("0000")) == 0 * Unit.count + assert UAS_IDS[0x81](b("0001")) == 1 * Unit.count + assert UAS_IDS[0x81](b("7FFF")) == 32767 * Unit.count + +def test_82(): + assert UAS_IDS[0x82](b("8000")) == -3276.8 * Unit.count + assert UAS_IDS[0x82](b("FFFF")) == -0.1 * Unit.count + assert UAS_IDS[0x82](b("0000")) == 0 * Unit.count + assert UAS_IDS[0x82](b("0001")) == 0.1 * Unit.count + assert float_equals(UAS_IDS[0x82](b("7FFF")), 3276.7 * Unit.count) + +def test_83(): + assert UAS_IDS[0x83](b("8000")) == -327.68 * Unit.count + assert UAS_IDS[0x83](b("FFFF")) == -0.01 * Unit.count + assert UAS_IDS[0x83](b("0000")) == 0 * Unit.count + assert UAS_IDS[0x83](b("0001")) == 0.01 * Unit.count + assert float_equals(UAS_IDS[0x83](b("7FFF")), 327.67 * Unit.count) + +def test_84(): + assert UAS_IDS[0x84](b("8000")) == -32.768 * Unit.count + assert UAS_IDS[0x84](b("FFFF")) == -0.001 * Unit.count + assert UAS_IDS[0x84](b("0000")) == 0 * Unit.count + assert UAS_IDS[0x84](b("0001")) == 0.001 * Unit.count + assert float_equals(UAS_IDS[0x84](b("7FFF")), 32.767 * Unit.count) + +def test_85(): + assert float_equals(UAS_IDS[0x85](b("8000")), -0.9999995 * Unit.count) + assert float_equals(UAS_IDS[0x85](b("FFFF")), -0.0000305 * Unit.count) + assert float_equals(UAS_IDS[0x85](b("0000")), 0 * Unit.count) + assert float_equals(UAS_IDS[0x85](b("0001")), 0.0000305 * Unit.count) + assert float_equals(UAS_IDS[0x85](b("7FFF")), 0.9999995 * Unit.count) + +def test_86(): + assert float_equals(UAS_IDS[0x86](b("8000")), -9.999995 * Unit.count) + assert float_equals(UAS_IDS[0x86](b("FFFF")), -0.000305 * Unit.count) + assert float_equals(UAS_IDS[0x86](b("0000")), 0 * Unit.count) + assert float_equals(UAS_IDS[0x86](b("0001")), 0.000305 * Unit.count) + assert float_equals(UAS_IDS[0x86](b("7FFF")), 9.999995 * Unit.count) + +def test_87(): + assert UAS_IDS[0x87](b("8000")) == -32768 * Unit.ppm + assert UAS_IDS[0x87](b("FFFF")) == -1 * Unit.ppm + assert UAS_IDS[0x87](b("0000")) == 0 * Unit.ppm + assert UAS_IDS[0x87](b("0001")) == 1 * Unit.ppm + assert UAS_IDS[0x87](b("7FFF")) == 32767 * Unit.ppm + +def test_8A(): + # the standard gives example values that don't line up perfectly + # with the scale. The last two tests here deviate from the standard + assert float_equals(UAS_IDS[0x8A](b("8000")), -3997.696 * Unit.millivolt) # -3999.998 mV + assert float_equals(UAS_IDS[0x8A](b("FFFF")), -0.122 * Unit.millivolt) + assert float_equals(UAS_IDS[0x8A](b("0000")), 0 * Unit.millivolt) + assert float_equals(UAS_IDS[0x8A](b("0001")), 0.122 * Unit.millivolt) + assert float_equals(UAS_IDS[0x8A](b("7FFF")), 3997.574 * Unit.millivolt) # 3999.876 mV + +def test_8B(): + assert UAS_IDS[0x8B](b("8000")) == -32.768 * Unit.volt + assert UAS_IDS[0x8B](b("FFFF")) == -0.001 * Unit.volt + assert UAS_IDS[0x8B](b("0000")) == 0 * Unit.volt + assert UAS_IDS[0x8B](b("0001")) == 0.001 * Unit.volt + assert UAS_IDS[0x8B](b("7FFF")) == 32.767 * Unit.volt + +def test_8C(): + assert UAS_IDS[0x8C](b("8000")) == -327.68 * Unit.volt + assert UAS_IDS[0x8C](b("FFFF")) == -0.01 * Unit.volt + assert UAS_IDS[0x8C](b("0000")) == 0 * Unit.volt + assert UAS_IDS[0x8C](b("0001")) == 0.01 * Unit.volt + assert UAS_IDS[0x8C](b("7FFF")) == 327.67 * Unit.volt + +def test_8D(): + assert float_equals(UAS_IDS[0x8D](b("8000")), -128 * Unit.milliampere) + assert float_equals(UAS_IDS[0x8D](b("FFFF")), -0.00390625 * Unit.milliampere) + assert float_equals(UAS_IDS[0x8D](b("0000")), 0 * Unit.milliampere) + assert float_equals(UAS_IDS[0x8D](b("0001")), 0.00390625 * Unit.milliampere) + assert float_equals(UAS_IDS[0x8D](b("7FFF")), 127.996 * Unit.milliampere) + +def test_8E(): + assert UAS_IDS[0x8E](b("8000")) == -32.768 * Unit.ampere + assert UAS_IDS[0x8E](b("FFFF")) == -0.001 * Unit.ampere + assert UAS_IDS[0x8E](b("0000")) == 0 * Unit.ampere + assert UAS_IDS[0x8E](b("0001")) == 0.001 * Unit.ampere + assert UAS_IDS[0x8E](b("7FFF")) == 32.767 * Unit.ampere + +def test_90(): + assert UAS_IDS[0x90](b("8000")) == -32768 * Unit.millisecond + assert UAS_IDS[0x90](b("FFFF")) == -1 * Unit.millisecond + assert UAS_IDS[0x90](b("0000")) == 0 * Unit.millisecond + assert UAS_IDS[0x90](b("0001")) == 1 * Unit.millisecond + assert UAS_IDS[0x90](b("7FFF")) == 32767 * Unit.millisecond + +def test_96(): + assert float_equals(UAS_IDS[0x96](b("8000")), Unit.Quantity(-3276.8, Unit.celsius)) + assert float_equals(UAS_IDS[0x96](b("FFFF")), Unit.Quantity(-0.1, Unit.celsius)) + assert float_equals(UAS_IDS[0x96](b("0000")), Unit.Quantity(0, Unit.celsius)) + assert float_equals(UAS_IDS[0x96](b("0001")), Unit.Quantity(0.1, Unit.celsius)) + assert float_equals(UAS_IDS[0x96](b("7FFF")), Unit.Quantity(3276.7, Unit.celsius)) + +def test_99(): + assert float_equals(UAS_IDS[0x99](b("8000")), -3276.8 * Unit.kilopascal) + assert float_equals(UAS_IDS[0x99](b("FFFF")), -0.1 * Unit.kilopascal) + assert float_equals(UAS_IDS[0x99](b("0000")), 0 * Unit.kilopascal) + assert float_equals(UAS_IDS[0x99](b("0001")), 0.1 * Unit.kilopascal) + assert float_equals(UAS_IDS[0x99](b("7FFF")), 3276.7 * Unit.kilopascal) + +def test_9C(): + assert UAS_IDS[0x9C](b("8000")) == -327.68 * Unit.degree + assert UAS_IDS[0x9C](b("FFFF")) == -0.01 * Unit.degree + assert UAS_IDS[0x9C](b("0000")) == 0 * Unit.degree + assert UAS_IDS[0x9C](b("0001")) == 0.01 * Unit.degree + assert UAS_IDS[0x9C](b("7FFF")) == 327.67 * Unit.degree + +def test_9D(): + assert UAS_IDS[0x9D](b("8000")) == -16384 * Unit.degree + assert UAS_IDS[0x9D](b("FFFF")) == -0.5 * Unit.degree + assert UAS_IDS[0x9D](b("0000")) == 0 * Unit.degree + assert UAS_IDS[0x9D](b("0001")) == 0.5 * Unit.degree + assert UAS_IDS[0x9D](b("7FFF")) == 16383.5 * Unit.degree + +def test_A8(): + assert UAS_IDS[0xA8](b("8000")) == -32768 * Unit.grams_per_second + assert UAS_IDS[0xA8](b("FFFF")) == -1 * Unit.grams_per_second + assert UAS_IDS[0xA8](b("0000")) == 0 * Unit.grams_per_second + assert UAS_IDS[0xA8](b("0001")) == 1 * Unit.grams_per_second + assert UAS_IDS[0xA8](b("7FFF")) == 32767 * Unit.grams_per_second + +def test_A9(): + assert UAS_IDS[0xA9](b("8000")) == -8192 * Unit.pascal / Unit.second + assert UAS_IDS[0xA9](b("FFFC")) == -1 * Unit.pascal / Unit.second + assert UAS_IDS[0xA9](b("0000")) == 0 * Unit.pascal / Unit.second + assert UAS_IDS[0xA9](b("0004")) == 1 * Unit.pascal / Unit.second + assert UAS_IDS[0xA9](b("7FFF")) == 8191.75 * Unit.pascal / Unit.second + +def test_AD(): + assert UAS_IDS[0xAD](b("8000")) == -327.68 * Unit.milligram + assert UAS_IDS[0xAD](b("FFFF")) == -0.01 * Unit.milligram + assert UAS_IDS[0xAD](b("0000")) == 0 * Unit.milligram + assert UAS_IDS[0xAD](b("0001")) == 0.01 * Unit.milligram + assert UAS_IDS[0xAD](b("7FFF")) == 327.67 * Unit.milligram + +def test_AE(): + assert UAS_IDS[0xAE](b("8000")) == -3276.8 * Unit.milligram + assert UAS_IDS[0xAE](b("FFFF")) == -0.1 * Unit.milligram + assert UAS_IDS[0xAE](b("0000")) == 0 * Unit.milligram + assert UAS_IDS[0xAE](b("0001")) == 0.1 * Unit.milligram + assert float_equals(UAS_IDS[0xAE](b("7FFF")), 3276.7 * Unit.milligram) + +def test_AF(): + assert UAS_IDS[0xAF](b("8000")) == -327.68 * Unit.percent + assert UAS_IDS[0xAF](b("FFFF")) == -0.01 * Unit.percent + assert UAS_IDS[0xAF](b("0000")) == 0 * Unit.percent + assert UAS_IDS[0xAF](b("0001")) == 0.01 * Unit.percent + assert UAS_IDS[0xAF](b("7FFF")) == 327.67 * Unit.percent + +def test_B0(): + assert UAS_IDS[0xB0](b("8000")) == -100.007936 * Unit.percent + assert UAS_IDS[0xB0](b("FFFF")) == -0.003052 * Unit.percent + assert UAS_IDS[0xB0](b("0000")) == 0 * Unit.percent + assert UAS_IDS[0xB0](b("0001")) == 0.003052 * Unit.percent + assert UAS_IDS[0xB0](b("7FFF")) == 100.004884 * Unit.percent + +def test_B1(): + assert UAS_IDS[0xB1](b("8000")) == -65536 * Unit.millivolt / Unit.second + assert UAS_IDS[0xB1](b("FFFF")) == -2 * Unit.millivolt / Unit.second + assert UAS_IDS[0xB1](b("0000")) == 0 * Unit.millivolt / Unit.second + assert UAS_IDS[0xB1](b("0001")) == 2 * Unit.millivolt / Unit.second + assert UAS_IDS[0xB1](b("7FFF")) == 65534 * Unit.millivolt / Unit.second + +def test_FC(): + assert UAS_IDS[0xFC](b("8000")) == -327.68 * Unit.kilopascal + assert UAS_IDS[0xFC](b("FFFF")) == -0.01 * Unit.kilopascal + assert UAS_IDS[0xFC](b("0000")) == 0 * Unit.kilopascal + assert UAS_IDS[0xFC](b("0001")) == 0.01 * Unit.kilopascal + assert UAS_IDS[0xFC](b("7FFF")) == 327.67 * Unit.kilopascal + +def test_FD(): + assert UAS_IDS[0xFD](b("8000")) == -32.768 * Unit.kilopascal + assert UAS_IDS[0xFD](b("FFFF")) == -0.001 * Unit.kilopascal + assert UAS_IDS[0xFD](b("0000")) == 0 * Unit.kilopascal + assert UAS_IDS[0xFD](b("0001")) == 0.001 * Unit.kilopascal + assert UAS_IDS[0xFD](b("7FFF")) == 32.767 * Unit.kilopascal + +def test_FE(): + assert UAS_IDS[0xFE](b("8000")) == -8192 * Unit.pascal + assert UAS_IDS[0xFE](b("FFFC")) == -1 * Unit.pascal + assert UAS_IDS[0xFE](b("0000")) == 0 * Unit.pascal + assert UAS_IDS[0xFE](b("0004")) == 1 * Unit.pascal + assert UAS_IDS[0xFE](b("7FFF")) == 8191.75 * Unit.pascal From 8ad96eeef94c1531e6c62802e6f4f00ec553a6bb Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Mon, 4 Jul 2016 22:22:20 -0400 Subject: [PATCH 070/128] skip mode 02 PID getters, added MID getters --- obd/commands.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/obd/commands.py b/obd/commands.py index d23bab5c..cb0259eb 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -160,6 +160,8 @@ c.command = b"02" + c.command[2:] # change the mode: 0100 ---> 0200 c.name = "DTC_" + c.name c.desc = "DTC " + c.desc + if c.decode == pid: + c.decode = drop # Never send mode 02 pid requests (use mode 01 instead) __mode2__.append(c) @@ -365,6 +367,7 @@ def base_commands(self): """ return [ self.PIDS_A, + self.MIDS_A, self.GET_DTC, self.CLEAR_DTC, self.GET_FREEZE_DTC, From 2f6ecca1ed4772edabcd16a3cfeb416567de8b8a Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Mon, 4 Jul 2016 22:30:31 -0400 Subject: [PATCH 071/128] simplified PID getter enumeration --- obd/commands.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/obd/commands.py b/obd/commands.py index cb0259eb..079cecab 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -380,14 +380,7 @@ def pid_getters(self): """ returns a list of PID GET commands """ getters = [] for mode in self.modes: - for cmd in mode: - - if cmd is None: - continue # this command is reserved - - if cmd.decode == pid: # GET commands have a special decoder - getters.append(cmd) - + getters += [ cmd for cmd in mode if (cmd and cmd.decode == pid) ] return getters From b11cc23df68bbd849d5e4ac8e7b7e26f9a7f9353 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Mon, 4 Jul 2016 22:44:48 -0400 Subject: [PATCH 072/128] don't bother type checking everything --- obd/commands.py | 45 ++++++++++++++---------------------------- tests/test_commands.py | 3 +-- 2 files changed, 16 insertions(+), 32 deletions(-) diff --git a/obd/commands.py b/obd/commands.py index 079cecab..574f5e10 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -349,15 +349,12 @@ def __getitem__(self, key): def __len__(self): """ returns the number of commands supported by python-OBD """ - l = 0 - for m in self.modes: - l += len(m) - return l + return sum([len(mode) for mode in self.modes]) - def __contains__(self, s): + def __contains__(self, name): """ calls has_name(s) """ - return self.has_name(s) + return self.has_name(name) def base_commands(self): @@ -395,38 +392,26 @@ def set_supported(self, mode, pid, v): def has_command(self, c): """ checks for existance of a command by OBDCommand object """ - if isinstance(c, OBDCommand): - return c in self.__dict__.values() - else: - logger.warning("has_command() only accepts OBDCommand objects") - return False + return c in self.__dict__.values() - def has_name(self, s): + def has_name(self, name): """ checks for existance of a command by name """ - if isinstance(s, str) or isinstance(s, unicode): - return s.isupper() and (s in self.__dict__.keys()) - else: - logger.warning("has_name() only accepts string names for commands") - return False + # isupper() rejects all the normal properties + return name.isupper() and name in self.__dict__ def has_pid(self, mode, pid): """ checks for existance of a command by int mode and int pid """ - if isinstance(mode, int) and isinstance(pid, int): - if (mode < 0) or (pid < 0): - return False - if mode >= len(self.modes): - return False - if pid >= len(self.modes[mode]): - return False - - # make sure that the command isn't reserved - return (self.modes[mode][pid] is not None) - - else: - logger.warning("has_pid() only accepts integer values for mode and PID") + if (mode < 0) or (pid < 0): + return False + if mode >= len(self.modes): return False + if pid >= len(self.modes[mode]): + return False + + # make sure that the command isn't reserved + return (self.modes[mode][pid] is not None) # export this object diff --git a/tests/test_commands.py b/tests/test_commands.py index dac06ade..1b257d55 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -80,11 +80,10 @@ def test_contains(): # by `in` assert cmd.name in obd.commands - # test things NOT in the tables, or invalid parameters + # test things NOT in the tables assert 'modes' not in obd.commands assert not obd.commands.has_pid(-1, 0) assert not obd.commands.has_pid(1, -1) - assert not obd.commands.has_command("I'm a string, not an OBDCommand") def test_pid_getters(): From 70deb94e22d76c03d5bc8ce756d1fb0f4142f6ff Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Tue, 5 Jul 2016 14:02:19 -0400 Subject: [PATCH 073/128] corrected name/description for mode 07 --- obd/commands.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/obd/commands.py b/obd/commands.py index 574f5e10..a416d3ca 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -280,8 +280,8 @@ ] __mode7__ = [ - # name description cmd bytes decoder ECU fast - OBDCommand("GET_FREEZE_DTC" , "Get Freeze DTCs" , b"07", 0, dtc, ECU.ALL, False), + # name description cmd bytes decoder ECU fast + OBDCommand("GET_CURRENT_DTC" , "Get DTCs from the current/last driving cycle" , b"07", 0, dtc, ECU.ALL, False), ] __mode9__ = [ From bbd14e4f6ca5cc1d0af637109e0b442fa8e08050 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Tue, 5 Jul 2016 14:13:43 -0400 Subject: [PATCH 074/128] updated command docs --- docs/Commands.md | 113 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 110 insertions(+), 3 deletions(-) diff --git a/docs/Commands.md b/docs/Commands.md index 8d422bf3..232b99b2 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -222,12 +222,119 @@ example output:
+# Mode 06 + +Mode 06 commands are used to monitor various test results from the vehicle. Currently, Mode 06 commands are only implemented for CAN protocols (ISO 15765-4). + +|PID | Name | Description | +|-------|-----------------------------|--------------------------------------------| +| 00 | MIDS_A | Supported MIDs [01-20] | +| 01 | MONITOR_O2_B1S1 | O2 Sensor Monitor Bank 1 - Sensor 1 | +| 02 | MONITOR_O2_B1S2 | O2 Sensor Monitor Bank 1 - Sensor 2 | +| 03 | MONITOR_O2_B1S3 | O2 Sensor Monitor Bank 1 - Sensor 3 | +| 04 | MONITOR_O2_B1S4 | O2 Sensor Monitor Bank 1 - Sensor 4 | +| 05 | MONITOR_O2_B2S1 | O2 Sensor Monitor Bank 2 - Sensor 1 | +| 06 | MONITOR_O2_B2S2 | O2 Sensor Monitor Bank 2 - Sensor 2 | +| 07 | MONITOR_O2_B2S3 | O2 Sensor Monitor Bank 2 - Sensor 3 | +| 08 | MONITOR_O2_B2S4 | O2 Sensor Monitor Bank 2 - Sensor 4 | +| 09 | MONITOR_O2_B3S1 | O2 Sensor Monitor Bank 3 - Sensor 1 | +| 0A | MONITOR_O2_B3S2 | O2 Sensor Monitor Bank 3 - Sensor 2 | +| 0B | MONITOR_O2_B3S3 | O2 Sensor Monitor Bank 3 - Sensor 3 | +| 0C | MONITOR_O2_B3S4 | O2 Sensor Monitor Bank 3 - Sensor 4 | +| 0D | MONITOR_O2_B4S1 | O2 Sensor Monitor Bank 4 - Sensor 1 | +| 0E | MONITOR_O2_B4S2 | O2 Sensor Monitor Bank 4 - Sensor 2 | +| 0F | MONITOR_O2_B4S3 | O2 Sensor Monitor Bank 4 - Sensor 3 | +| 10 | MONITOR_O2_B4S4 | O2 Sensor Monitor Bank 4 - Sensor 4 | +| *gap* | | | +| 20 | MIDS_B | Supported MIDs [21-40] | +| 21 | MONITOR_CATALYST_B1 | Catalyst Monitor Bank 1 | +| 22 | MONITOR_CATALYST_B2 | Catalyst Monitor Bank 2 | +| 23 | MONITOR_CATALYST_B3 | Catalyst Monitor Bank 3 | +| 24 | MONITOR_CATALYST_B4 | Catalyst Monitor Bank 4 | +| *gap* | | | +| 31 | MONITOR_EGR_B1 | EGR Monitor Bank 1 | +| 32 | MONITOR_EGR_B2 | EGR Monitor Bank 2 | +| 33 | MONITOR_EGR_B3 | EGR Monitor Bank 3 | +| 34 | MONITOR_EGR_B4 | EGR Monitor Bank 4 | +| 35 | MONITOR_VVT_B1 | VVT Monitor Bank 1 | +| 36 | MONITOR_VVT_B2 | VVT Monitor Bank 2 | +| 37 | MONITOR_VVT_B3 | VVT Monitor Bank 3 | +| 38 | MONITOR_VVT_B4 | VVT Monitor Bank 4 | +| 39 | MONITOR_EVAP_150 | EVAP Monitor (Cap Off / 0.150\") | +| 3A | MONITOR_EVAP_090 | EVAP Monitor (0.090\") | +| 3B | MONITOR_EVAP_040 | EVAP Monitor (0.040\") | +| 3C | MONITOR_EVAP_020 | EVAP Monitor (0.020\") | +| 3D | MONITOR_PURGE_FLOW | Purge Flow Monitor | +| *gap* | | | +| 40 | MIDS_C | Supported MIDs [41-60] | +| 41 | MONITOR_O2_HEATER_B1S1 | O2 Sensor Heater Monitor Bank 1 - Sensor 1 | +| 42 | MONITOR_O2_HEATER_B1S2 | O2 Sensor Heater Monitor Bank 1 - Sensor 2 | +| 43 | MONITOR_O2_HEATER_B1S3 | O2 Sensor Heater Monitor Bank 1 - Sensor 3 | +| 44 | MONITOR_O2_HEATER_B1S4 | O2 Sensor Heater Monitor Bank 1 - Sensor 4 | +| 45 | MONITOR_O2_HEATER_B2S1 | O2 Sensor Heater Monitor Bank 2 - Sensor 1 | +| 46 | MONITOR_O2_HEATER_B2S2 | O2 Sensor Heater Monitor Bank 2 - Sensor 2 | +| 47 | MONITOR_O2_HEATER_B2S3 | O2 Sensor Heater Monitor Bank 2 - Sensor 3 | +| 48 | MONITOR_O2_HEATER_B2S4 | O2 Sensor Heater Monitor Bank 2 - Sensor 4 | +| 49 | MONITOR_O2_HEATER_B3S1 | O2 Sensor Heater Monitor Bank 3 - Sensor 1 | +| 4A | MONITOR_O2_HEATER_B3S2 | O2 Sensor Heater Monitor Bank 3 - Sensor 2 | +| 4B | MONITOR_O2_HEATER_B3S3 | O2 Sensor Heater Monitor Bank 3 - Sensor 3 | +| 4C | MONITOR_O2_HEATER_B3S4 | O2 Sensor Heater Monitor Bank 3 - Sensor 4 | +| 4D | MONITOR_O2_HEATER_B4S1 | O2 Sensor Heater Monitor Bank 4 - Sensor 1 | +| 4E | MONITOR_O2_HEATER_B4S2 | O2 Sensor Heater Monitor Bank 4 - Sensor 2 | +| 4F | MONITOR_O2_HEATER_B4S3 | O2 Sensor Heater Monitor Bank 4 - Sensor 3 | +| 50 | MONITOR_O2_HEATER_B4S4 | O2 Sensor Heater Monitor Bank 4 - Sensor 4 | +| *gap* | | | +| 60 | MIDS_D | Supported MIDs [61-80] | +| 61 | MONITOR_HEATED_CATALYST_B1 | Heated Catalyst Monitor Bank 1 | +| 62 | MONITOR_HEATED_CATALYST_B2 | Heated Catalyst Monitor Bank 2 | +| 63 | MONITOR_HEATED_CATALYST_B3 | Heated Catalyst Monitor Bank 3 | +| 64 | MONITOR_HEATED_CATALYST_B4 | Heated Catalyst Monitor Bank 4 | +| *gap* | | | +| 71 | MONITOR_SECONDARY_AIR_1 | Secondary Air Monitor 1 | +| 72 | MONITOR_SECONDARY_AIR_2 | Secondary Air Monitor 2 | +| 73 | MONITOR_SECONDARY_AIR_3 | Secondary Air Monitor 3 | +| 74 | MONITOR_SECONDARY_AIR_4 | Secondary Air Monitor 4 | +| *gap* | | | +| 80 | MIDS_E | Supported MIDs [81-A0] | +| 81 | MONITOR_FUEL_SYSTEM_B1 | Fuel System Monitor Bank 1 | +| 82 | MONITOR_FUEL_SYSTEM_B2 | Fuel System Monitor Bank 2 | +| 83 | MONITOR_FUEL_SYSTEM_B3 | Fuel System Monitor Bank 3 | +| 84 | MONITOR_FUEL_SYSTEM_B4 | Fuel System Monitor Bank 4 | +| 85 | MONITOR_BOOST_PRESSURE_B1 | Boost Pressure Control Monitor Bank 1 | +| 86 | MONITOR_BOOST_PRESSURE_B2 | Boost Pressure Control Monitor Bank 1 | +| *gap* | | | +| 90 | MONITOR_NOX_ABSORBER_B1 | NOx Absorber Monitor Bank 1 | +| 91 | MONITOR_NOX_ABSORBER_B2 | NOx Absorber Monitor Bank 2 | +| *gap* | | | +| 98 | MONITOR_NOX_CATALYST_B1 | NOx Catalyst Monitor Bank 1 | +| 99 | MONITOR_NOX_CATALYST_B2 | NOx Catalyst Monitor Bank 2 | +| *gap* | | | +| A0 | MIDS_F | Supported MIDs [A1-C0] | +| A1 | MONITOR_MISFIRE_GENERAL | Misfire Monitor General Data | +| A2 | MONITOR_MISFIRE_CYLINDER_1 | Misfire Cylinder 1 Data | +| A3 | MONITOR_MISFIRE_CYLINDER_2 | Misfire Cylinder 2 Data | +| A4 | MONITOR_MISFIRE_CYLINDER_3 | Misfire Cylinder 3 Data | +| A5 | MONITOR_MISFIRE_CYLINDER_4 | Misfire Cylinder 4 Data | +| A6 | MONITOR_MISFIRE_CYLINDER_5 | Misfire Cylinder 5 Data | +| A7 | MONITOR_MISFIRE_CYLINDER_6 | Misfire Cylinder 6 Data | +| A8 | MONITOR_MISFIRE_CYLINDER_7 | Misfire Cylinder 7 Data | +| A9 | MONITOR_MISFIRE_CYLINDER_8 | Misfire Cylinder 8 Data | +| AA | MONITOR_MISFIRE_CYLINDER_9 | Misfire Cylinder 9 Data | +| AB | MONITOR_MISFIRE_CYLINDER_10 | Misfire Cylinder 10 Data | +| AC | MONITOR_MISFIRE_CYLINDER_11 | Misfire Cylinder 11 Data | +| AD | MONITOR_MISFIRE_CYLINDER_12 | Misfire Cylinder 12 Data | +| *gap* | | | +| B0 | MONITOR_PM_FILTER_B1 | PM Filter Monitor Bank 1 | +| B1 | MONITOR_PM_FILTER_B2 | PM Filter Monitor Bank 2 | + +
+ # Mode 07 The return value will be encoded in the same structure as the Mode 03 `GET_DTC` command. -|PID | Name | Description | -|-----|----------------|------------------------------| -| N/A | GET_FREEZE_DTC | Get Freeze DTCs | +|PID | Name | Description | +|-----|-----------------|----------------------------------------------| +| N/A | GET_CURRENT_DTC | Get DTCs from the current/last driving cycle |
From dd6dbd84f871f2e79e058afdf38757c9441fcac4 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Tue, 5 Jul 2016 15:30:46 -0400 Subject: [PATCH 075/128] added response value column to commands docs --- docs/Commands.md | 196 +++++++++++++++++++++++------------------------ 1 file changed, 98 insertions(+), 98 deletions(-) diff --git a/docs/Commands.md b/docs/Commands.md index 232b99b2..2c029e88 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -72,104 +72,104 @@ obd.commands.has_pid(1, 12) # True # Mode 01 -|PID | Name | Description | -|----|---------------------------|-----------------------------------------| -| 00 | PIDS_A | Supported PIDs [01-20] | -| 01 | STATUS | Status since DTCs cleared | -| 02 | FREEZE_DTC | DTC that triggered the freeze frame | -| 03 | FUEL_STATUS | Fuel System Status | -| 04 | ENGINE_LOAD | Calculated Engine Load | -| 05 | COOLANT_TEMP | Engine Coolant Temperature | -| 06 | SHORT_FUEL_TRIM_1 | Short Term Fuel Trim - Bank 1 | -| 07 | LONG_FUEL_TRIM_1 | Long Term Fuel Trim - Bank 1 | -| 08 | SHORT_FUEL_TRIM_2 | Short Term Fuel Trim - Bank 2 | -| 09 | LONG_FUEL_TRIM_2 | Long Term Fuel Trim - Bank 2 | -| 0A | FUEL_PRESSURE | Fuel Pressure | -| 0B | INTAKE_PRESSURE | Intake Manifold Pressure | -| 0C | RPM | Engine RPM | -| 0D | SPEED | Vehicle Speed | -| 0E | TIMING_ADVANCE | Timing Advance | -| 0F | INTAKE_TEMP | Intake Air Temp | -| 10 | MAF | Air Flow Rate (MAF) | -| 11 | THROTTLE_POS | Throttle Position | -| 12 | AIR_STATUS | Secondary Air Status | -| 13 | O2_SENSORS | O2 Sensors Present | -| 14 | O2_B1S1 | O2: Bank 1 - Sensor 1 Voltage | -| 15 | O2_B1S2 | O2: Bank 1 - Sensor 2 Voltage | -| 16 | O2_B1S3 | O2: Bank 1 - Sensor 3 Voltage | -| 17 | O2_B1S4 | O2: Bank 1 - Sensor 4 Voltage | -| 18 | O2_B2S1 | O2: Bank 2 - Sensor 1 Voltage | -| 19 | O2_B2S2 | O2: Bank 2 - Sensor 2 Voltage | -| 1A | O2_B2S3 | O2: Bank 2 - Sensor 3 Voltage | -| 1B | O2_B2S4 | O2: Bank 2 - Sensor 4 Voltage | -| 1C | OBD_COMPLIANCE | OBD Standards Compliance | -| 1D | O2_SENSORS_ALT | O2 Sensors Present (alternate) | -| 1E | AUX_INPUT_STATUS | Auxiliary input status (power take off) | -| 1F | RUN_TIME | Engine Run Time | -| 20 | PIDS_B | Supported PIDs [21-40] | -| 21 | DISTANCE_W_MIL | Distance Traveled with MIL on | -| 22 | FUEL_RAIL_PRESSURE_VAC | Fuel Rail Pressure (relative to vacuum) | -| 23 | FUEL_RAIL_PRESSURE_DIRECT | Fuel Rail Pressure (direct inject) | -| 24 | O2_S1_WR_VOLTAGE | 02 Sensor 1 WR Lambda Voltage | -| 25 | O2_S2_WR_VOLTAGE | 02 Sensor 2 WR Lambda Voltage | -| 26 | O2_S3_WR_VOLTAGE | 02 Sensor 3 WR Lambda Voltage | -| 27 | O2_S4_WR_VOLTAGE | 02 Sensor 4 WR Lambda Voltage | -| 28 | O2_S5_WR_VOLTAGE | 02 Sensor 5 WR Lambda Voltage | -| 29 | O2_S6_WR_VOLTAGE | 02 Sensor 6 WR Lambda Voltage | -| 2A | O2_S7_WR_VOLTAGE | 02 Sensor 7 WR Lambda Voltage | -| 2B | O2_S8_WR_VOLTAGE | 02 Sensor 8 WR Lambda Voltage | -| 2C | COMMANDED_EGR | Commanded EGR | -| 2D | EGR_ERROR | EGR Error | -| 2E | EVAPORATIVE_PURGE | Commanded Evaporative Purge | -| 2F | FUEL_LEVEL | Fuel Level Input | -| 30 | WARMUPS_SINCE_DTC_CLEAR | Number of warm-ups since codes cleared | -| 31 | DISTANCE_SINCE_DTC_CLEAR | Distance traveled since codes cleared | -| 32 | EVAP_VAPOR_PRESSURE | Evaporative system vapor pressure | -| 33 | BAROMETRIC_PRESSURE | Barometric Pressure | -| 34 | O2_S1_WR_CURRENT | 02 Sensor 1 WR Lambda Current | -| 35 | O2_S2_WR_CURRENT | 02 Sensor 2 WR Lambda Current | -| 36 | O2_S3_WR_CURRENT | 02 Sensor 3 WR Lambda Current | -| 37 | O2_S4_WR_CURRENT | 02 Sensor 4 WR Lambda Current | -| 38 | O2_S5_WR_CURRENT | 02 Sensor 5 WR Lambda Current | -| 39 | O2_S6_WR_CURRENT | 02 Sensor 6 WR Lambda Current | -| 3A | O2_S7_WR_CURRENT | 02 Sensor 7 WR Lambda Current | -| 3B | O2_S8_WR_CURRENT | 02 Sensor 8 WR Lambda Current | -| 3C | CATALYST_TEMP_B1S1 | Catalyst Temperature: Bank 1 - Sensor 1 | -| 3D | CATALYST_TEMP_B2S1 | Catalyst Temperature: Bank 2 - Sensor 1 | -| 3E | CATALYST_TEMP_B1S2 | Catalyst Temperature: Bank 1 - Sensor 2 | -| 3F | CATALYST_TEMP_B2S2 | Catalyst Temperature: Bank 2 - Sensor 2 | -| 40 | PIDS_C | Supported PIDs [41-60] | -| 41 | *unsupported* | *unsupported* | -| 42 | *unsupported* | *unsupported* | -| 43 | *unsupported* | *unsupported* | -| 44 | *unsupported* | *unsupported* | -| 45 | RELATIVE_THROTTLE_POS | Relative throttle position | -| 46 | AMBIANT_AIR_TEMP | Ambient air temperature | -| 47 | THROTTLE_POS_B | Absolute throttle position B | -| 48 | THROTTLE_POS_C | Absolute throttle position C | -| 49 | ACCELERATOR_POS_D | Accelerator pedal position D | -| 4A | ACCELERATOR_POS_E | Accelerator pedal position E | -| 4B | ACCELERATOR_POS_F | Accelerator pedal position F | -| 4C | THROTTLE_ACTUATOR | Commanded throttle actuator | -| 4D | RUN_TIME_MIL | Time run with MIL on | -| 4E | TIME_SINCE_DTC_CLEARED | Time since trouble codes cleared | -| 4F | *unsupported* | *unsupported* | -| 50 | MAX_MAF | Maximum value for mass air flow sensor | -| 51 | FUEL_TYPE | Fuel Type | -| 52 | ETHANOL_PERCENT | Ethanol Fuel Percent | -| 53 | EVAP_VAPOR_PRESSURE_ABS | Absolute Evap system Vapor Pressure | -| 54 | EVAP_VAPOR_PRESSURE_ALT | Evap system vapor pressure | -| 55 | SHORT_O2_TRIM_B1 | Short term secondary O2 trim - Bank 1 | -| 56 | LONG_O2_TRIM_B1 | Long term secondary O2 trim - Bank 1 | -| 57 | SHORT_O2_TRIM_B2 | Short term secondary O2 trim - Bank 2 | -| 58 | LONG_O2_TRIM_B2 | Long term secondary O2 trim - Bank 2 | -| 59 | FUEL_RAIL_PRESSURE_ABS | Fuel rail pressure (absolute) | -| 5A | RELATIVE_ACCEL_POS | Relative accelerator pedal position | -| 5B | HYBRID_BATTERY_REMAINING | Hybrid battery pack remaining life | -| 5C | OIL_TEMP | Engine oil temperature | -| 5D | FUEL_INJECT_TIMING | Fuel injection timing | -| 5E | FUEL_RATE | Engine fuel rate | -| 5F | *unsupported* | *unsupported* | +|PID | Name | Description | Response Value | +|----|---------------------------|-----------------------------------------|-----------------------| +| 00 | PIDS_A | Supported PIDs [01-20] | bitstring | +| 01 | STATUS | Status since DTCs cleared | | +| 02 | FREEZE_DTC | DTC that triggered the freeze frame | | +| 03 | FUEL_STATUS | Fuel System Status | string | +| 04 | ENGINE_LOAD | Calculated Engine Load | Unit.percent | +| 05 | COOLANT_TEMP | Engine Coolant Temperature | Unit.celsius | +| 06 | SHORT_FUEL_TRIM_1 | Short Term Fuel Trim - Bank 1 | Unit.percent | +| 07 | LONG_FUEL_TRIM_1 | Long Term Fuel Trim - Bank 1 | Unit.percent | +| 08 | SHORT_FUEL_TRIM_2 | Short Term Fuel Trim - Bank 2 | Unit.percent | +| 09 | LONG_FUEL_TRIM_2 | Long Term Fuel Trim - Bank 2 | Unit.percent | +| 0A | FUEL_PRESSURE | Fuel Pressure | Unit.kilopascal | +| 0B | INTAKE_PRESSURE | Intake Manifold Pressure | Unit.kilopascal | +| 0C | RPM | Engine RPM | Unit.rpm | +| 0D | SPEED | Vehicle Speed | Unit.kph | +| 0E | TIMING_ADVANCE | Timing Advance | Unit.degree | +| 0F | INTAKE_TEMP | Intake Air Temp | Unit.celsius | +| 10 | MAF | Air Flow Rate (MAF) | Unit.grams_per_second | +| 11 | THROTTLE_POS | Throttle Position | Unit.percent | +| 12 | AIR_STATUS | Secondary Air Status | string | +| 13 | O2_SENSORS | O2 Sensors Present | | +| 14 | O2_B1S1 | O2: Bank 1 - Sensor 1 Voltage | Unit.volt | +| 15 | O2_B1S2 | O2: Bank 1 - Sensor 2 Voltage | Unit.volt | +| 16 | O2_B1S3 | O2: Bank 1 - Sensor 3 Voltage | Unit.volt | +| 17 | O2_B1S4 | O2: Bank 1 - Sensor 4 Voltage | Unit.volt | +| 18 | O2_B2S1 | O2: Bank 2 - Sensor 1 Voltage | Unit.volt | +| 19 | O2_B2S2 | O2: Bank 2 - Sensor 2 Voltage | Unit.volt | +| 1A | O2_B2S3 | O2: Bank 2 - Sensor 3 Voltage | Unit.volt | +| 1B | O2_B2S4 | O2: Bank 2 - Sensor 4 Voltage | Unit.volt | +| 1C | OBD_COMPLIANCE | OBD Standards Compliance | string | +| 1D | O2_SENSORS_ALT | O2 Sensors Present (alternate) | | +| 1E | AUX_INPUT_STATUS | Auxiliary input status (power take off) | boolean | +| 1F | RUN_TIME | Engine Run Time | Unit.second | +| 20 | PIDS_B | Supported PIDs [21-40] | bitstring | +| 21 | DISTANCE_W_MIL | Distance Traveled with MIL on | Unit.kilometer | +| 22 | FUEL_RAIL_PRESSURE_VAC | Fuel Rail Pressure (relative to vacuum) | Unit.kilopascal | +| 23 | FUEL_RAIL_PRESSURE_DIRECT | Fuel Rail Pressure (direct inject) | Unit.kilopascal | +| 24 | O2_S1_WR_VOLTAGE | 02 Sensor 1 WR Lambda Voltage | Unit.volt | +| 25 | O2_S2_WR_VOLTAGE | 02 Sensor 2 WR Lambda Voltage | Unit.volt | +| 26 | O2_S3_WR_VOLTAGE | 02 Sensor 3 WR Lambda Voltage | Unit.volt | +| 27 | O2_S4_WR_VOLTAGE | 02 Sensor 4 WR Lambda Voltage | Unit.volt | +| 28 | O2_S5_WR_VOLTAGE | 02 Sensor 5 WR Lambda Voltage | Unit.volt | +| 29 | O2_S6_WR_VOLTAGE | 02 Sensor 6 WR Lambda Voltage | Unit.volt | +| 2A | O2_S7_WR_VOLTAGE | 02 Sensor 7 WR Lambda Voltage | Unit.volt | +| 2B | O2_S8_WR_VOLTAGE | 02 Sensor 8 WR Lambda Voltage | Unit.volt | +| 2C | COMMANDED_EGR | Commanded EGR | Unit.percent | +| 2D | EGR_ERROR | EGR Error | Unit.percent | +| 2E | EVAPORATIVE_PURGE | Commanded Evaporative Purge | Unit.percent | +| 2F | FUEL_LEVEL | Fuel Level Input | Unit.percent | +| 30 | WARMUPS_SINCE_DTC_CLEAR | Number of warm-ups since codes cleared | Unit.count | +| 31 | DISTANCE_SINCE_DTC_CLEAR | Distance traveled since codes cleared | Unit.kilometer | +| 32 | EVAP_VAPOR_PRESSURE | Evaporative system vapor pressure | Unit.pascal | +| 33 | BAROMETRIC_PRESSURE | Barometric Pressure | Unit.kilopascal | +| 34 | O2_S1_WR_CURRENT | 02 Sensor 1 WR Lambda Current | Unit.milliampere | +| 35 | O2_S2_WR_CURRENT | 02 Sensor 2 WR Lambda Current | Unit.milliampere | +| 36 | O2_S3_WR_CURRENT | 02 Sensor 3 WR Lambda Current | Unit.milliampere | +| 37 | O2_S4_WR_CURRENT | 02 Sensor 4 WR Lambda Current | Unit.milliampere | +| 38 | O2_S5_WR_CURRENT | 02 Sensor 5 WR Lambda Current | Unit.milliampere | +| 39 | O2_S6_WR_CURRENT | 02 Sensor 6 WR Lambda Current | Unit.milliampere | +| 3A | O2_S7_WR_CURRENT | 02 Sensor 7 WR Lambda Current | Unit.milliampere | +| 3B | O2_S8_WR_CURRENT | 02 Sensor 8 WR Lambda Current | Unit.milliampere | +| 3C | CATALYST_TEMP_B1S1 | Catalyst Temperature: Bank 1 - Sensor 1 | Unit.celsius | +| 3D | CATALYST_TEMP_B2S1 | Catalyst Temperature: Bank 2 - Sensor 1 | Unit.celsius | +| 3E | CATALYST_TEMP_B1S2 | Catalyst Temperature: Bank 1 - Sensor 2 | Unit.celsius | +| 3F | CATALYST_TEMP_B2S2 | Catalyst Temperature: Bank 2 - Sensor 2 | Unit.celsius | +| 40 | PIDS_C | Supported PIDs [41-60] | bitstring | +| 41 | *unsupported* | *unsupported* | | +| 42 | *unsupported* | *unsupported* | | +| 43 | *unsupported* | *unsupported* | | +| 44 | *unsupported* | *unsupported* | | +| 45 | RELATIVE_THROTTLE_POS | Relative throttle position | Unit.percent | +| 46 | AMBIANT_AIR_TEMP | Ambient air temperature | Unit.celsius | +| 47 | THROTTLE_POS_B | Absolute throttle position B | Unit.percent | +| 48 | THROTTLE_POS_C | Absolute throttle position C | Unit.percent | +| 49 | ACCELERATOR_POS_D | Accelerator pedal position D | Unit.percent | +| 4A | ACCELERATOR_POS_E | Accelerator pedal position E | Unit.percent | +| 4B | ACCELERATOR_POS_F | Accelerator pedal position F | Unit.percent | +| 4C | THROTTLE_ACTUATOR | Commanded throttle actuator | Unit.percent | +| 4D | RUN_TIME_MIL | Time run with MIL on | Unit.minute | +| 4E | TIME_SINCE_DTC_CLEARED | Time since trouble codes cleared | Unit.minute | +| 4F | *unsupported* | *unsupported* | | +| 50 | MAX_MAF | Maximum value for mass air flow sensor | Unit.grams_per_second | +| 51 | FUEL_TYPE | Fuel Type | string | +| 52 | ETHANOL_PERCENT | Ethanol Fuel Percent | Unit.percent | +| 53 | EVAP_VAPOR_PRESSURE_ABS | Absolute Evap system Vapor Pressure | Unit.kilopascal | +| 54 | EVAP_VAPOR_PRESSURE_ALT | Evap system vapor pressure | Unit.pascal | +| 55 | SHORT_O2_TRIM_B1 | Short term secondary O2 trim - Bank 1 | Unit.percent | +| 56 | LONG_O2_TRIM_B1 | Long term secondary O2 trim - Bank 1 | Unit.percent | +| 57 | SHORT_O2_TRIM_B2 | Short term secondary O2 trim - Bank 2 | Unit.percent | +| 58 | LONG_O2_TRIM_B2 | Long term secondary O2 trim - Bank 2 | Unit.percent | +| 59 | FUEL_RAIL_PRESSURE_ABS | Fuel rail pressure (absolute) | Unit.kilopascal | +| 5A | RELATIVE_ACCEL_POS | Relative accelerator pedal position | Unit.percent | +| 5B | HYBRID_BATTERY_REMAINING | Hybrid battery pack remaining life | Unit.percent | +| 5C | OIL_TEMP | Engine oil temperature | Unit.celsius | +| 5D | FUEL_INJECT_TIMING | Fuel injection timing | Unit.degree | +| 5E | FUEL_RATE | Engine fuel rate | Unit.liters_per_hour | +| 5F | *unsupported* | *unsupported* | |
From 1ea15ba80ca9835699faecd5a4e208ad25a761ad Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Tue, 5 Jul 2016 15:36:43 -0400 Subject: [PATCH 076/128] use empty tuple to bank-align O2 sensor presence --- obd/decoders.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/obd/decoders.py b/obd/decoders.py index 74a1c0bf..e95a02ab 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -202,6 +202,7 @@ def o2_sensors(messages): d = messages[0].data bitstring = bytes_to_bits(d) return ( + (), # bank 0 is invalid tuple([ b == "1" for b in bitstring[:4] ]), # bank 1 tuple([ b == "1" for b in bitstring[4:] ]), # bank 2 ) @@ -215,6 +216,7 @@ def o2_sensors_alt(messages): d = messages[0].data bitstring = bytes_to_bits(d) return ( + (), # bank 0 is invalid tuple([ b == "1" for b in bitstring[:2] ]), # bank 1 tuple([ b == "1" for b in bitstring[2:4] ]), # bank 2 tuple([ b == "1" for b in bitstring[4:6] ]), # bank 3 From 818dd65db0a08e9ebbc45892c97236e851fb30f3 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Tue, 5 Jul 2016 15:48:05 -0400 Subject: [PATCH 077/128] documented O2 sensor presence --- docs/Commands.md | 4 ++-- docs/Responses.md | 27 ++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/docs/Commands.md b/docs/Commands.md index 2c029e88..88891a48 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -93,7 +93,7 @@ obd.commands.has_pid(1, 12) # True | 10 | MAF | Air Flow Rate (MAF) | Unit.grams_per_second | | 11 | THROTTLE_POS | Throttle Position | Unit.percent | | 12 | AIR_STATUS | Secondary Air Status | string | -| 13 | O2_SENSORS | O2 Sensors Present | | +| 13 | O2_SENSORS | O2 Sensors Present | [special](Responses.md#oxygen-sensors-present) | | 14 | O2_B1S1 | O2: Bank 1 - Sensor 1 Voltage | Unit.volt | | 15 | O2_B1S2 | O2: Bank 1 - Sensor 2 Voltage | Unit.volt | | 16 | O2_B1S3 | O2: Bank 1 - Sensor 3 Voltage | Unit.volt | @@ -103,7 +103,7 @@ obd.commands.has_pid(1, 12) # True | 1A | O2_B2S3 | O2: Bank 2 - Sensor 3 Voltage | Unit.volt | | 1B | O2_B2S4 | O2: Bank 2 - Sensor 4 Voltage | Unit.volt | | 1C | OBD_COMPLIANCE | OBD Standards Compliance | string | -| 1D | O2_SENSORS_ALT | O2 Sensors Present (alternate) | | +| 1D | O2_SENSORS_ALT | O2 Sensors Present (alternate) | [special](Responses.md#oxygen-sensors-present) | | 1E | AUX_INPUT_STATUS | Auxiliary input status (power take off) | boolean | | 1F | RUN_TIME | Engine Run Time | Unit.second | | 20 | PIDS_B | Supported PIDs [21-40] | bitstring | diff --git a/docs/Responses.md b/docs/Responses.md index 1cb292cd..5580b84c 100644 --- a/docs/Responses.md +++ b/docs/Responses.md @@ -25,7 +25,7 @@ if not r.is_null(): --- -# Values +# Pint Values The `value` property typically contains a [Pint](http://pint.readthedocs.io/en/latest/) `Quantity` object, but can also hold complex structures (depending on the request). Pint quantities combine a value and unit into a single class, and are used to represent physical values (such as "4 seconds", and "88 mph"). This allows for consistency when doing math and unit conversions. Pint maintains a registry of units, which is exposed in python-OBD as `obd.Unit`. @@ -69,4 +69,29 @@ import obd --- + +# Oxygen Sensors Present + +Returns a 2D structure of tuples (representing bank and sensor number), that holds boolean values for sensor presence. + +```python +# obd.commands.O2_SENSORS +responce.value = ( + (), # bank 0 is invalid, this is merely for correct indexing + (True, True, True, False), # bank 1 + (False, False, False, False) # bank 2 +) + +# obd.commands.O2_SENSORS_ALT +responce.value = ( + (), # bank 0 is invalid, this is merely for correct indexing + (True, True), # bank 1 + (True, False), # bank 2 + (False, False), # bank 2 + (False, False) # bank 2 +) +``` + +--- +
From e5bbb672d18f87cb897ed79ae13623bd5cb1b965 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Tue, 5 Jul 2016 15:59:38 -0400 Subject: [PATCH 078/128] return full DTC tuple in FREEZE_DTC, use "" for unknown DTC, fixed tests --- obd/commands.py | 2 +- obd/decoders.py | 11 +++-------- tests/test_decoders.py | 24 ++++++++++++------------ 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/obd/commands.py b/obd/commands.py index a416d3ca..dbab7605 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -367,7 +367,7 @@ def base_commands(self): self.MIDS_A, self.GET_DTC, self.CLEAR_DTC, - self.GET_FREEZE_DTC, + self.GET_CURRENT_DTC, self.ELM_VERSION, self.ELM_VOLTAGE, ] diff --git a/obd/decoders.py b/obd/decoders.py index e95a02ab..3e19b583 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -377,7 +377,8 @@ def single_dtc(_bytes): dtc += str( (_bytes[0] >> 4) & 0b0011 ) # the next pair of 2 bits. Mask off the bits we read above dtc += bytes_to_hex(_bytes)[1:4] - return dtc + # pull a description if we have one + return (dtc, DTC.get(dtc, "")) def dtc(messages): @@ -395,13 +396,7 @@ def dtc(messages): dtc = single_dtc( (d[n-1], d[n]) ) if dtc is not None: - # pull a description if we have one - if dtc in DTC: - desc = DTC[dtc] - else: - desc = "Unknown error code" - - codes.append( (dtc, desc) ) + codes.append(dtc) return codes diff --git a/tests/test_decoders.py b/tests/test_decoders.py index 66b7dcc2..9f36ce53 100644 --- a/tests/test_decoders.py +++ b/tests/test_decoders.py @@ -125,16 +125,16 @@ def test_air_status(): assert d.air_status(m("03")) == None def test_o2_sensors(): - assert d.o2_sensors(m("00")) == ((False, False, False, False), (False, False, False, False)) - assert d.o2_sensors(m("01")) == ((False, False, False, False), (False, False, False, True)) - assert d.o2_sensors(m("0F")) == ((False, False, False, False), (True, True, True, True)) - assert d.o2_sensors(m("F0")) == ((True, True, True, True), (False, False, False, False)) + assert d.o2_sensors(m("00")) == ((),(False, False, False, False), (False, False, False, False)) + assert d.o2_sensors(m("01")) == ((),(False, False, False, False), (False, False, False, True)) + assert d.o2_sensors(m("0F")) == ((),(False, False, False, False), (True, True, True, True)) + assert d.o2_sensors(m("F0")) == ((),(True, True, True, True), (False, False, False, False)) def test_o2_sensors_alt(): - assert d.o2_sensors_alt(m("00")) == ((False, False), (False, False), (False, False), (False, False)) - assert d.o2_sensors_alt(m("01")) == ((False, False), (False, False), (False, False), (False, True)) - assert d.o2_sensors_alt(m("0F")) == ((False, False), (False, False), (True, True), (True, True)) - assert d.o2_sensors_alt(m("F0")) == ((True, True), (True, True), (False, False), (False, False)) + assert d.o2_sensors_alt(m("00")) == ((),(False, False), (False, False), (False, False), (False, False)) + assert d.o2_sensors_alt(m("01")) == ((),(False, False), (False, False), (False, False), (False, True)) + assert d.o2_sensors_alt(m("0F")) == ((),(False, False), (False, False), (True, True), (True, True)) + assert d.o2_sensors_alt(m("F0")) == ((),(True, True), (True, True), (False, False), (False, False)) def test_aux_input_status(): assert d.aux_input_status(m("00")) == False @@ -154,14 +154,14 @@ def test_dtc(): # multiple codes assert d.dtc(m("010480034123")) == [ ("P0104", "Mass or Volume Air Flow Circuit Intermittent"), - ("B0003", "Unknown error code"), - ("C0123", "Unknown error code"), + ("B0003", ""), # unknown error codes return empty strings + ("C0123", ""), ] # invalid code lengths are dropped assert d.dtc(m("0104800341")) == [ ("P0104", "Mass or Volume Air Flow Circuit Intermittent"), - ("B0003", "Unknown error code"), + ("B0003", ""), ] # 0000 codes are dropped @@ -172,7 +172,7 @@ def test_dtc(): # test multiple messages assert d.dtc(m("0104") + m("8003") + m("0000")) == [ ("P0104", "Mass or Volume Air Flow Circuit Intermittent"), - ("B0003", "Unknown error code"), + ("B0003", ""), ] def test_monitor(): From 9810b4d0d946aef6ef7ae04e680cd7c4be8ca5d4 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Tue, 5 Jul 2016 16:12:31 -0400 Subject: [PATCH 079/128] fixed single_dtc decoder --- obd/decoders.py | 10 ++++++++-- tests/test_decoders.py | 6 ++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/obd/decoders.py b/obd/decoders.py index 3e19b583..a0501cd6 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -359,7 +359,7 @@ def fuel_type(_hex): return v -def single_dtc(_bytes): +def parse_dtc(_bytes): """ converts 2 bytes into a DTC code """ # check validity (also ignores padding that the ELM returns) @@ -381,6 +381,12 @@ def single_dtc(_bytes): return (dtc, DTC.get(dtc, "")) +def single_dtc(messages): + """ parses a single DTC from a message """ + d = messages[0].data + return parse_dtc(d) + + def dtc(messages): """ converts a frame of 2-byte DTCs into a list of DTCs """ codes = [] @@ -393,7 +399,7 @@ def dtc(messages): for n in range(1, len(d), 2): # parse the code - dtc = single_dtc( (d[n-1], d[n]) ) + dtc = parse_dtc( (d[n-1], d[n]) ) if dtc is not None: codes.append(dtc) diff --git a/tests/test_decoders.py b/tests/test_decoders.py index 9f36ce53..1735391d 100644 --- a/tests/test_decoders.py +++ b/tests/test_decoders.py @@ -146,6 +146,12 @@ def test_elm_voltage(): assert d.elm_voltage([ Message([ Frame("12") ]) ]) == 12 * Unit.volt assert d.elm_voltage([ Message([ Frame("12ABCD") ]) ]) == None +def test_single_dtc(): + assert d.single_dtc(m("0104")) == ("P0104", "Mass or Volume Air Flow Circuit Intermittent") + assert d.single_dtc(m("4123")) == ("C0123", "") + assert d.single_dtc(m("01")) == None + assert d.single_dtc(m("010400")) == None + def test_dtc(): assert d.dtc(m("0104")) == [ ("P0104", "Mass or Volume Air Flow Circuit Intermittent"), From f4788a6c7d0ca9092ddc2412aeb9ccf2a356aa91 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Tue, 5 Jul 2016 16:36:52 -0400 Subject: [PATCH 080/128] adding more links and response value descriptions --- docs/Commands.md | 230 +++++++++++++++++++++------------------------- docs/Responses.md | 26 ++++++ 2 files changed, 133 insertions(+), 123 deletions(-) diff --git a/docs/Commands.md b/docs/Commands.md index 88891a48..bd39dd4f 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -63,10 +63,10 @@ obd.commands.has_pid(1, 12) # True # OBD-II adapter (ELM327 commands) -|PID | Name | Description | -|-----|-------------|-----------------------------------------| -| N/A | ELM_VERSION | OBD-II adapter version string | -| N/A | ELM_VOLTAGE | Voltage detected by OBD-II adapter | +|PID | Name | Description | Response Value | +|-----|-------------|-----------------------------------------|-----------------------| +| N/A | ELM_VERSION | OBD-II adapter version string | string | +| N/A | ELM_VOLTAGE | Voltage detected by OBD-II adapter | Unit.volt |
@@ -76,7 +76,7 @@ obd.commands.has_pid(1, 12) # True |----|---------------------------|-----------------------------------------|-----------------------| | 00 | PIDS_A | Supported PIDs [01-20] | bitstring | | 01 | STATUS | Status since DTCs cleared | | -| 02 | FREEZE_DTC | DTC that triggered the freeze frame | | +| 02 | FREEZE_DTC | DTC that triggered the freeze frame | [special](Responses.md#diagnostic-trouble-codes-dtcs) | | 03 | FUEL_STATUS | Fuel System Status | string | | 04 | ENGINE_LOAD | Calculated Engine Load | Unit.percent | | 05 | COOLANT_TEMP | Engine Coolant Temperature | Unit.celsius | @@ -189,143 +189,127 @@ obd.commands.DTC_RPM # the Mode 02 command # Mode 03 -Mode 03 contains a single command `GET_DTC` which requests all diagnostic trouble codes from the vehicle's engine. +Mode 03 contains a single command `GET_DTC` which requests all diagnostic trouble codes from the vehicle. The response will contain the codes themselves, as well as a description (if python-OBD has one). See the [DTC Responses](Responses.md#diagnostic-trouble-codes-dtcs) section for more details. -|PID | Name | Description | -|-----|---------|-----------------------------------------| -| N/A | GET_DTC | Get Diagnostic Trouble Codes | +|PID | Name | Description | Response Value | +|-----|---------|-----------------------------------------|-----------------------| +| N/A | GET_DTC | Get Diagnostic Trouble Codes | [special](Responses.md#diagnostic-trouble-codes-dtcs) | -This command requests all diagnostic trouble codes from the vehicle's engine. The `value` field of the response object will contain a list of tuples, where each tuple contains the DTC, and a string description of that DTC (if available). - -```python -import obd -connection = obd.OBD() -r = connection.query(obd.commands.GET_DTC) -print(r.value) - -''' -example output: -[ - ("P0030", "HO2S Heater Control Circuit"), - ("P1367", "Unknown error code") -] -''' -```
# Mode 04 -|PID | Name | Description | -|-----|-----------|-----------------------------------------| -| N/A | CLEAR_DTC | Clear DTCs and Freeze data | +|PID | Name | Description | Response Value | +|-----|-----------|-----------------------------------------|-----------------------| +| N/A | CLEAR_DTC | Clear DTCs and Freeze data | N/A |
# Mode 06 -Mode 06 commands are used to monitor various test results from the vehicle. Currently, Mode 06 commands are only implemented for CAN protocols (ISO 15765-4). - -|PID | Name | Description | -|-------|-----------------------------|--------------------------------------------| -| 00 | MIDS_A | Supported MIDs [01-20] | -| 01 | MONITOR_O2_B1S1 | O2 Sensor Monitor Bank 1 - Sensor 1 | -| 02 | MONITOR_O2_B1S2 | O2 Sensor Monitor Bank 1 - Sensor 2 | -| 03 | MONITOR_O2_B1S3 | O2 Sensor Monitor Bank 1 - Sensor 3 | -| 04 | MONITOR_O2_B1S4 | O2 Sensor Monitor Bank 1 - Sensor 4 | -| 05 | MONITOR_O2_B2S1 | O2 Sensor Monitor Bank 2 - Sensor 1 | -| 06 | MONITOR_O2_B2S2 | O2 Sensor Monitor Bank 2 - Sensor 2 | -| 07 | MONITOR_O2_B2S3 | O2 Sensor Monitor Bank 2 - Sensor 3 | -| 08 | MONITOR_O2_B2S4 | O2 Sensor Monitor Bank 2 - Sensor 4 | -| 09 | MONITOR_O2_B3S1 | O2 Sensor Monitor Bank 3 - Sensor 1 | -| 0A | MONITOR_O2_B3S2 | O2 Sensor Monitor Bank 3 - Sensor 2 | -| 0B | MONITOR_O2_B3S3 | O2 Sensor Monitor Bank 3 - Sensor 3 | -| 0C | MONITOR_O2_B3S4 | O2 Sensor Monitor Bank 3 - Sensor 4 | -| 0D | MONITOR_O2_B4S1 | O2 Sensor Monitor Bank 4 - Sensor 1 | -| 0E | MONITOR_O2_B4S2 | O2 Sensor Monitor Bank 4 - Sensor 2 | -| 0F | MONITOR_O2_B4S3 | O2 Sensor Monitor Bank 4 - Sensor 3 | -| 10 | MONITOR_O2_B4S4 | O2 Sensor Monitor Bank 4 - Sensor 4 | +Mode 06 commands are used to monitor various test results from the vehicle. All commands in this mode return the same datatype, as described in the [Monitor Response](Responses.md#monitors-mode-06-responses) section. Currently, mode 06 commands are only implemented for CAN protocols (ISO 15765-4). + +|PID | Name | Description | Response Value | +|-------|-----------------------------|--------------------------------------------|-----------------------| +| 00 | MIDS_A | Supported MIDs [01-20] | bitstring | +| 01 | MONITOR_O2_B1S1 | O2 Sensor Monitor Bank 1 - Sensor 1 | [monitor](Responses.md#monitors-mode-06-responses) | +| 02 | MONITOR_O2_B1S2 | O2 Sensor Monitor Bank 1 - Sensor 2 | [monitor](Responses.md#monitors-mode-06-responses) | +| 03 | MONITOR_O2_B1S3 | O2 Sensor Monitor Bank 1 - Sensor 3 | [monitor](Responses.md#monitors-mode-06-responses) | +| 04 | MONITOR_O2_B1S4 | O2 Sensor Monitor Bank 1 - Sensor 4 | [monitor](Responses.md#monitors-mode-06-responses) | +| 05 | MONITOR_O2_B2S1 | O2 Sensor Monitor Bank 2 - Sensor 1 | [monitor](Responses.md#monitors-mode-06-responses) | +| 06 | MONITOR_O2_B2S2 | O2 Sensor Monitor Bank 2 - Sensor 2 | [monitor](Responses.md#monitors-mode-06-responses) | +| 07 | MONITOR_O2_B2S3 | O2 Sensor Monitor Bank 2 - Sensor 3 | [monitor](Responses.md#monitors-mode-06-responses) | +| 08 | MONITOR_O2_B2S4 | O2 Sensor Monitor Bank 2 - Sensor 4 | [monitor](Responses.md#monitors-mode-06-responses) | +| 09 | MONITOR_O2_B3S1 | O2 Sensor Monitor Bank 3 - Sensor 1 | [monitor](Responses.md#monitors-mode-06-responses) | +| 0A | MONITOR_O2_B3S2 | O2 Sensor Monitor Bank 3 - Sensor 2 | [monitor](Responses.md#monitors-mode-06-responses) | +| 0B | MONITOR_O2_B3S3 | O2 Sensor Monitor Bank 3 - Sensor 3 | [monitor](Responses.md#monitors-mode-06-responses) | +| 0C | MONITOR_O2_B3S4 | O2 Sensor Monitor Bank 3 - Sensor 4 | [monitor](Responses.md#monitors-mode-06-responses) | +| 0D | MONITOR_O2_B4S1 | O2 Sensor Monitor Bank 4 - Sensor 1 | [monitor](Responses.md#monitors-mode-06-responses) | +| 0E | MONITOR_O2_B4S2 | O2 Sensor Monitor Bank 4 - Sensor 2 | [monitor](Responses.md#monitors-mode-06-responses) | +| 0F | MONITOR_O2_B4S3 | O2 Sensor Monitor Bank 4 - Sensor 3 | [monitor](Responses.md#monitors-mode-06-responses) | +| 10 | MONITOR_O2_B4S4 | O2 Sensor Monitor Bank 4 - Sensor 4 | [monitor](Responses.md#monitors-mode-06-responses) | | *gap* | | | -| 20 | MIDS_B | Supported MIDs [21-40] | -| 21 | MONITOR_CATALYST_B1 | Catalyst Monitor Bank 1 | -| 22 | MONITOR_CATALYST_B2 | Catalyst Monitor Bank 2 | -| 23 | MONITOR_CATALYST_B3 | Catalyst Monitor Bank 3 | -| 24 | MONITOR_CATALYST_B4 | Catalyst Monitor Bank 4 | +| 20 | MIDS_B | Supported MIDs [21-40] | bitstring | +| 21 | MONITOR_CATALYST_B1 | Catalyst Monitor Bank 1 | [monitor](Responses.md#monitors-mode-06-responses) | +| 22 | MONITOR_CATALYST_B2 | Catalyst Monitor Bank 2 | [monitor](Responses.md#monitors-mode-06-responses) | +| 23 | MONITOR_CATALYST_B3 | Catalyst Monitor Bank 3 | [monitor](Responses.md#monitors-mode-06-responses) | +| 24 | MONITOR_CATALYST_B4 | Catalyst Monitor Bank 4 | [monitor](Responses.md#monitors-mode-06-responses) | | *gap* | | | -| 31 | MONITOR_EGR_B1 | EGR Monitor Bank 1 | -| 32 | MONITOR_EGR_B2 | EGR Monitor Bank 2 | -| 33 | MONITOR_EGR_B3 | EGR Monitor Bank 3 | -| 34 | MONITOR_EGR_B4 | EGR Monitor Bank 4 | -| 35 | MONITOR_VVT_B1 | VVT Monitor Bank 1 | -| 36 | MONITOR_VVT_B2 | VVT Monitor Bank 2 | -| 37 | MONITOR_VVT_B3 | VVT Monitor Bank 3 | -| 38 | MONITOR_VVT_B4 | VVT Monitor Bank 4 | -| 39 | MONITOR_EVAP_150 | EVAP Monitor (Cap Off / 0.150\") | -| 3A | MONITOR_EVAP_090 | EVAP Monitor (0.090\") | -| 3B | MONITOR_EVAP_040 | EVAP Monitor (0.040\") | -| 3C | MONITOR_EVAP_020 | EVAP Monitor (0.020\") | -| 3D | MONITOR_PURGE_FLOW | Purge Flow Monitor | +| 31 | MONITOR_EGR_B1 | EGR Monitor Bank 1 | [monitor](Responses.md#monitors-mode-06-responses) | +| 32 | MONITOR_EGR_B2 | EGR Monitor Bank 2 | [monitor](Responses.md#monitors-mode-06-responses) | +| 33 | MONITOR_EGR_B3 | EGR Monitor Bank 3 | [monitor](Responses.md#monitors-mode-06-responses) | +| 34 | MONITOR_EGR_B4 | EGR Monitor Bank 4 | [monitor](Responses.md#monitors-mode-06-responses) | +| 35 | MONITOR_VVT_B1 | VVT Monitor Bank 1 | [monitor](Responses.md#monitors-mode-06-responses) | +| 36 | MONITOR_VVT_B2 | VVT Monitor Bank 2 | [monitor](Responses.md#monitors-mode-06-responses) | +| 37 | MONITOR_VVT_B3 | VVT Monitor Bank 3 | [monitor](Responses.md#monitors-mode-06-responses) | +| 38 | MONITOR_VVT_B4 | VVT Monitor Bank 4 | [monitor](Responses.md#monitors-mode-06-responses) | +| 39 | MONITOR_EVAP_150 | EVAP Monitor (Cap Off / 0.150\") | [monitor](Responses.md#monitors-mode-06-responses) | +| 3A | MONITOR_EVAP_090 | EVAP Monitor (0.090\") | [monitor](Responses.md#monitors-mode-06-responses) | +| 3B | MONITOR_EVAP_040 | EVAP Monitor (0.040\") | [monitor](Responses.md#monitors-mode-06-responses) | +| 3C | MONITOR_EVAP_020 | EVAP Monitor (0.020\") | [monitor](Responses.md#monitors-mode-06-responses) | +| 3D | MONITOR_PURGE_FLOW | Purge Flow Monitor | [monitor](Responses.md#monitors-mode-06-responses) | | *gap* | | | -| 40 | MIDS_C | Supported MIDs [41-60] | -| 41 | MONITOR_O2_HEATER_B1S1 | O2 Sensor Heater Monitor Bank 1 - Sensor 1 | -| 42 | MONITOR_O2_HEATER_B1S2 | O2 Sensor Heater Monitor Bank 1 - Sensor 2 | -| 43 | MONITOR_O2_HEATER_B1S3 | O2 Sensor Heater Monitor Bank 1 - Sensor 3 | -| 44 | MONITOR_O2_HEATER_B1S4 | O2 Sensor Heater Monitor Bank 1 - Sensor 4 | -| 45 | MONITOR_O2_HEATER_B2S1 | O2 Sensor Heater Monitor Bank 2 - Sensor 1 | -| 46 | MONITOR_O2_HEATER_B2S2 | O2 Sensor Heater Monitor Bank 2 - Sensor 2 | -| 47 | MONITOR_O2_HEATER_B2S3 | O2 Sensor Heater Monitor Bank 2 - Sensor 3 | -| 48 | MONITOR_O2_HEATER_B2S4 | O2 Sensor Heater Monitor Bank 2 - Sensor 4 | -| 49 | MONITOR_O2_HEATER_B3S1 | O2 Sensor Heater Monitor Bank 3 - Sensor 1 | -| 4A | MONITOR_O2_HEATER_B3S2 | O2 Sensor Heater Monitor Bank 3 - Sensor 2 | -| 4B | MONITOR_O2_HEATER_B3S3 | O2 Sensor Heater Monitor Bank 3 - Sensor 3 | -| 4C | MONITOR_O2_HEATER_B3S4 | O2 Sensor Heater Monitor Bank 3 - Sensor 4 | -| 4D | MONITOR_O2_HEATER_B4S1 | O2 Sensor Heater Monitor Bank 4 - Sensor 1 | -| 4E | MONITOR_O2_HEATER_B4S2 | O2 Sensor Heater Monitor Bank 4 - Sensor 2 | -| 4F | MONITOR_O2_HEATER_B4S3 | O2 Sensor Heater Monitor Bank 4 - Sensor 3 | -| 50 | MONITOR_O2_HEATER_B4S4 | O2 Sensor Heater Monitor Bank 4 - Sensor 4 | +| 40 | MIDS_C | Supported MIDs [41-60] | bitstring | +| 41 | MONITOR_O2_HEATER_B1S1 | O2 Sensor Heater Monitor Bank 1 - Sensor 1 | [monitor](Responses.md#monitors-mode-06-responses) | +| 42 | MONITOR_O2_HEATER_B1S2 | O2 Sensor Heater Monitor Bank 1 - Sensor 2 | [monitor](Responses.md#monitors-mode-06-responses) | +| 43 | MONITOR_O2_HEATER_B1S3 | O2 Sensor Heater Monitor Bank 1 - Sensor 3 | [monitor](Responses.md#monitors-mode-06-responses) | +| 44 | MONITOR_O2_HEATER_B1S4 | O2 Sensor Heater Monitor Bank 1 - Sensor 4 | [monitor](Responses.md#monitors-mode-06-responses) | +| 45 | MONITOR_O2_HEATER_B2S1 | O2 Sensor Heater Monitor Bank 2 - Sensor 1 | [monitor](Responses.md#monitors-mode-06-responses) | +| 46 | MONITOR_O2_HEATER_B2S2 | O2 Sensor Heater Monitor Bank 2 - Sensor 2 | [monitor](Responses.md#monitors-mode-06-responses) | +| 47 | MONITOR_O2_HEATER_B2S3 | O2 Sensor Heater Monitor Bank 2 - Sensor 3 | [monitor](Responses.md#monitors-mode-06-responses) | +| 48 | MONITOR_O2_HEATER_B2S4 | O2 Sensor Heater Monitor Bank 2 - Sensor 4 | [monitor](Responses.md#monitors-mode-06-responses) | +| 49 | MONITOR_O2_HEATER_B3S1 | O2 Sensor Heater Monitor Bank 3 - Sensor 1 | [monitor](Responses.md#monitors-mode-06-responses) | +| 4A | MONITOR_O2_HEATER_B3S2 | O2 Sensor Heater Monitor Bank 3 - Sensor 2 | [monitor](Responses.md#monitors-mode-06-responses) | +| 4B | MONITOR_O2_HEATER_B3S3 | O2 Sensor Heater Monitor Bank 3 - Sensor 3 | [monitor](Responses.md#monitors-mode-06-responses) | +| 4C | MONITOR_O2_HEATER_B3S4 | O2 Sensor Heater Monitor Bank 3 - Sensor 4 | [monitor](Responses.md#monitors-mode-06-responses) | +| 4D | MONITOR_O2_HEATER_B4S1 | O2 Sensor Heater Monitor Bank 4 - Sensor 1 | [monitor](Responses.md#monitors-mode-06-responses) | +| 4E | MONITOR_O2_HEATER_B4S2 | O2 Sensor Heater Monitor Bank 4 - Sensor 2 | [monitor](Responses.md#monitors-mode-06-responses) | +| 4F | MONITOR_O2_HEATER_B4S3 | O2 Sensor Heater Monitor Bank 4 - Sensor 3 | [monitor](Responses.md#monitors-mode-06-responses) | +| 50 | MONITOR_O2_HEATER_B4S4 | O2 Sensor Heater Monitor Bank 4 - Sensor 4 | [monitor](Responses.md#monitors-mode-06-responses) | | *gap* | | | -| 60 | MIDS_D | Supported MIDs [61-80] | -| 61 | MONITOR_HEATED_CATALYST_B1 | Heated Catalyst Monitor Bank 1 | -| 62 | MONITOR_HEATED_CATALYST_B2 | Heated Catalyst Monitor Bank 2 | -| 63 | MONITOR_HEATED_CATALYST_B3 | Heated Catalyst Monitor Bank 3 | -| 64 | MONITOR_HEATED_CATALYST_B4 | Heated Catalyst Monitor Bank 4 | +| 60 | MIDS_D | Supported MIDs [61-80] | bitstring | +| 61 | MONITOR_HEATED_CATALYST_B1 | Heated Catalyst Monitor Bank 1 | [monitor](Responses.md#monitors-mode-06-responses) | +| 62 | MONITOR_HEATED_CATALYST_B2 | Heated Catalyst Monitor Bank 2 | [monitor](Responses.md#monitors-mode-06-responses) | +| 63 | MONITOR_HEATED_CATALYST_B3 | Heated Catalyst Monitor Bank 3 | [monitor](Responses.md#monitors-mode-06-responses) | +| 64 | MONITOR_HEATED_CATALYST_B4 | Heated Catalyst Monitor Bank 4 | [monitor](Responses.md#monitors-mode-06-responses) | | *gap* | | | -| 71 | MONITOR_SECONDARY_AIR_1 | Secondary Air Monitor 1 | -| 72 | MONITOR_SECONDARY_AIR_2 | Secondary Air Monitor 2 | -| 73 | MONITOR_SECONDARY_AIR_3 | Secondary Air Monitor 3 | -| 74 | MONITOR_SECONDARY_AIR_4 | Secondary Air Monitor 4 | +| 71 | MONITOR_SECONDARY_AIR_1 | Secondary Air Monitor 1 | [monitor](Responses.md#monitors-mode-06-responses) | +| 72 | MONITOR_SECONDARY_AIR_2 | Secondary Air Monitor 2 | [monitor](Responses.md#monitors-mode-06-responses) | +| 73 | MONITOR_SECONDARY_AIR_3 | Secondary Air Monitor 3 | [monitor](Responses.md#monitors-mode-06-responses) | +| 74 | MONITOR_SECONDARY_AIR_4 | Secondary Air Monitor 4 | [monitor](Responses.md#monitors-mode-06-responses) | | *gap* | | | -| 80 | MIDS_E | Supported MIDs [81-A0] | -| 81 | MONITOR_FUEL_SYSTEM_B1 | Fuel System Monitor Bank 1 | -| 82 | MONITOR_FUEL_SYSTEM_B2 | Fuel System Monitor Bank 2 | -| 83 | MONITOR_FUEL_SYSTEM_B3 | Fuel System Monitor Bank 3 | -| 84 | MONITOR_FUEL_SYSTEM_B4 | Fuel System Monitor Bank 4 | -| 85 | MONITOR_BOOST_PRESSURE_B1 | Boost Pressure Control Monitor Bank 1 | -| 86 | MONITOR_BOOST_PRESSURE_B2 | Boost Pressure Control Monitor Bank 1 | +| 80 | MIDS_E | Supported MIDs [81-A0] | bitstring | +| 81 | MONITOR_FUEL_SYSTEM_B1 | Fuel System Monitor Bank 1 | [monitor](Responses.md#monitors-mode-06-responses) | +| 82 | MONITOR_FUEL_SYSTEM_B2 | Fuel System Monitor Bank 2 | [monitor](Responses.md#monitors-mode-06-responses) | +| 83 | MONITOR_FUEL_SYSTEM_B3 | Fuel System Monitor Bank 3 | [monitor](Responses.md#monitors-mode-06-responses) | +| 84 | MONITOR_FUEL_SYSTEM_B4 | Fuel System Monitor Bank 4 | [monitor](Responses.md#monitors-mode-06-responses) | +| 85 | MONITOR_BOOST_PRESSURE_B1 | Boost Pressure Control Monitor Bank 1 | [monitor](Responses.md#monitors-mode-06-responses) | +| 86 | MONITOR_BOOST_PRESSURE_B2 | Boost Pressure Control Monitor Bank 1 | [monitor](Responses.md#monitors-mode-06-responses) | | *gap* | | | -| 90 | MONITOR_NOX_ABSORBER_B1 | NOx Absorber Monitor Bank 1 | -| 91 | MONITOR_NOX_ABSORBER_B2 | NOx Absorber Monitor Bank 2 | +| 90 | MONITOR_NOX_ABSORBER_B1 | NOx Absorber Monitor Bank 1 | [monitor](Responses.md#monitors-mode-06-responses) | +| 91 | MONITOR_NOX_ABSORBER_B2 | NOx Absorber Monitor Bank 2 | [monitor](Responses.md#monitors-mode-06-responses) | | *gap* | | | -| 98 | MONITOR_NOX_CATALYST_B1 | NOx Catalyst Monitor Bank 1 | -| 99 | MONITOR_NOX_CATALYST_B2 | NOx Catalyst Monitor Bank 2 | +| 98 | MONITOR_NOX_CATALYST_B1 | NOx Catalyst Monitor Bank 1 | [monitor](Responses.md#monitors-mode-06-responses) | +| 99 | MONITOR_NOX_CATALYST_B2 | NOx Catalyst Monitor Bank 2 | [monitor](Responses.md#monitors-mode-06-responses) | | *gap* | | | -| A0 | MIDS_F | Supported MIDs [A1-C0] | -| A1 | MONITOR_MISFIRE_GENERAL | Misfire Monitor General Data | -| A2 | MONITOR_MISFIRE_CYLINDER_1 | Misfire Cylinder 1 Data | -| A3 | MONITOR_MISFIRE_CYLINDER_2 | Misfire Cylinder 2 Data | -| A4 | MONITOR_MISFIRE_CYLINDER_3 | Misfire Cylinder 3 Data | -| A5 | MONITOR_MISFIRE_CYLINDER_4 | Misfire Cylinder 4 Data | -| A6 | MONITOR_MISFIRE_CYLINDER_5 | Misfire Cylinder 5 Data | -| A7 | MONITOR_MISFIRE_CYLINDER_6 | Misfire Cylinder 6 Data | -| A8 | MONITOR_MISFIRE_CYLINDER_7 | Misfire Cylinder 7 Data | -| A9 | MONITOR_MISFIRE_CYLINDER_8 | Misfire Cylinder 8 Data | -| AA | MONITOR_MISFIRE_CYLINDER_9 | Misfire Cylinder 9 Data | -| AB | MONITOR_MISFIRE_CYLINDER_10 | Misfire Cylinder 10 Data | -| AC | MONITOR_MISFIRE_CYLINDER_11 | Misfire Cylinder 11 Data | -| AD | MONITOR_MISFIRE_CYLINDER_12 | Misfire Cylinder 12 Data | +| A0 | MIDS_F | Supported MIDs [A1-C0] | bitstring | +| A1 | MONITOR_MISFIRE_GENERAL | Misfire Monitor General Data | [monitor](Responses.md#monitors-mode-06-responses) | +| A2 | MONITOR_MISFIRE_CYLINDER_1 | Misfire Cylinder 1 Data | [monitor](Responses.md#monitors-mode-06-responses) | +| A3 | MONITOR_MISFIRE_CYLINDER_2 | Misfire Cylinder 2 Data | [monitor](Responses.md#monitors-mode-06-responses) | +| A4 | MONITOR_MISFIRE_CYLINDER_3 | Misfire Cylinder 3 Data | [monitor](Responses.md#monitors-mode-06-responses) | +| A5 | MONITOR_MISFIRE_CYLINDER_4 | Misfire Cylinder 4 Data | [monitor](Responses.md#monitors-mode-06-responses) | +| A6 | MONITOR_MISFIRE_CYLINDER_5 | Misfire Cylinder 5 Data | [monitor](Responses.md#monitors-mode-06-responses) | +| A7 | MONITOR_MISFIRE_CYLINDER_6 | Misfire Cylinder 6 Data | [monitor](Responses.md#monitors-mode-06-responses) | +| A8 | MONITOR_MISFIRE_CYLINDER_7 | Misfire Cylinder 7 Data | [monitor](Responses.md#monitors-mode-06-responses) | +| A9 | MONITOR_MISFIRE_CYLINDER_8 | Misfire Cylinder 8 Data | [monitor](Responses.md#monitors-mode-06-responses) | +| AA | MONITOR_MISFIRE_CYLINDER_9 | Misfire Cylinder 9 Data | [monitor](Responses.md#monitors-mode-06-responses) | +| AB | MONITOR_MISFIRE_CYLINDER_10 | Misfire Cylinder 10 Data | [monitor](Responses.md#monitors-mode-06-responses) | +| AC | MONITOR_MISFIRE_CYLINDER_11 | Misfire Cylinder 11 Data | [monitor](Responses.md#monitors-mode-06-responses) | +| AD | MONITOR_MISFIRE_CYLINDER_12 | Misfire Cylinder 12 Data | [monitor](Responses.md#monitors-mode-06-responses) | | *gap* | | | -| B0 | MONITOR_PM_FILTER_B1 | PM Filter Monitor Bank 1 | -| B1 | MONITOR_PM_FILTER_B2 | PM Filter Monitor Bank 2 | +| B0 | MONITOR_PM_FILTER_B1 | PM Filter Monitor Bank 1 | [monitor](Responses.md#monitors-mode-06-responses) | +| B1 | MONITOR_PM_FILTER_B2 | PM Filter Monitor Bank 2 | [monitor](Responses.md#monitors-mode-06-responses) |
@@ -333,8 +317,8 @@ Mode 06 commands are used to monitor various test results from the vehicle. Curr The return value will be encoded in the same structure as the Mode 03 `GET_DTC` command. -|PID | Name | Description | -|-----|-----------------|----------------------------------------------| -| N/A | GET_CURRENT_DTC | Get DTCs from the current/last driving cycle | +|PID | Name | Description | Response Value | +|-----|-----------------|----------------------------------------------|-----------------------| +| N/A | GET_CURRENT_DTC | Get DTCs from the current/last driving cycle | [special](Responses.md#diagnostic-trouble-codes-dtcs) |
diff --git a/docs/Responses.md b/docs/Responses.md index 5580b84c..f545568f 100644 --- a/docs/Responses.md +++ b/docs/Responses.md @@ -69,6 +69,23 @@ import obd --- +# Diagnostic Trouble Codes (DTCs) + +Each DTC is represented by a tuple containing the DTC code, and a description (if python-OBD has one). When multiple DTCs are returned, they are stored in a list. + +```python +# obd.commands.GET_DTC +responce.value = [ + ("P0104", "Mass or Volume Air Flow Circuit Intermittent"), + ("B0003", ""), # unknown error code, it's probably vehicle-specific + ("C0123", "") +] + +# obd.commands.FREEZE_DTC +responce.value = ("P0104", "Mass or Volume Air Flow Circuit Intermittent") +``` + +--- # Oxygen Sensors Present @@ -91,6 +108,15 @@ responce.value = ( (False, False) # bank 2 ) ``` +--- + +# Monitors (Mode 06 Responses) + +All mode 06 commands return `Monitor` objects holding various test results for the requested sensor. A single monitor response can hold multiple tests. + +```python +# TODO +``` --- From 5de345ab9520528a7662a185e9b414e9f4608e5a Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Tue, 5 Jul 2016 16:45:27 -0400 Subject: [PATCH 081/128] added usage example to O2 sensors present --- docs/Responses.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Responses.md b/docs/Responses.md index f545568f..c7fa52d1 100644 --- a/docs/Responses.md +++ b/docs/Responses.md @@ -107,6 +107,9 @@ responce.value = ( (False, False), # bank 2 (False, False) # bank 2 ) + +# example usage: +response.value[1][2] == True # Bank 1, Sensor 2 is present ``` --- From 52ab6b8ee25d973e331f50b6179e0bf89e66c5d5 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Tue, 5 Jul 2016 17:02:34 -0400 Subject: [PATCH 082/128] added module layout, updated basic usage code --- README.md | 3 ++- docs/index.md | 22 ++++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c6fe81a8..11862c8c 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,8 @@ cmd = obd.commands.RPM # select an OBD command (sensor) response = connection.query(cmd) # send the command, and parse the response -print(response.value) +print(response.value) # returns unit-bearing values thanks to Pint +print(response.value.magnitude) # or simple floats ``` Documentation diff --git a/docs/index.md b/docs/index.md index 05282dce..2b1583f6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -31,14 +31,32 @@ cmd = obd.commands.RPM # select an OBD command (sensor) response = connection.query(cmd) # send the command, and parse the response -print(response.value) -print(response.unit) +print(response.value) # returns unit-bearing values thanks to Pint +print(response.value.magnitude) # or simple floats ``` OBD connections operate in a request-reply fashion. To retrieve data from the car, you must send commands that query for the data you want (e.g. RPM, Vehicle speed, etc). In python-OBD, this is done with the `query()` function. The commands themselves are represented as objects, and can be looked up by name or value in `obd.commands`. The `query()` function will return a response object with parsed data in its `value` and `unit` properties.
+# Module Layout + +```python +import obd + +obd.OBD # main OBD connection class +obd.Async # asynchronous OBD connection class +obd.commands # command tables +obd.Unit # unit tables (a Pint UnitRegistry) +obd.logger # the OBD module's root logger (for debug) +obd.OBDStatus # enum for connection status +obd.scan_serial # util function for manually scanning for OBD adapters +obd.OBDCommand # class for making your own OBD Commands +obd.ECU # enum for marking which ECU a command should listen to +``` + +
+ # License GNU General Public License V2 From 0ef0ee4a5e0c9d8418f7808a340a48cf810719f9 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Tue, 5 Jul 2016 17:05:07 -0400 Subject: [PATCH 083/128] removed old set_supported() function --- obd/commands.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/obd/commands.py b/obd/commands.py index dbab7605..86dcf94e 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -381,15 +381,6 @@ def pid_getters(self): return getters - def set_supported(self, mode, pid, v): - """ sets the boolean supported flag for the given command """ - if isinstance(v, bool): - if self.has(mode, pid): - self.modes[mode][pid].supported = v - else: - logger.warning("set_supported() only accepts boolean values") - - def has_command(self, c): """ checks for existance of a command by OBDCommand object """ return c in self.__dict__.values() From 55eaad095731b60c4127c728a68d521f0e21787e Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Tue, 5 Jul 2016 18:26:08 -0400 Subject: [PATCH 084/128] upcase TID names, lookup tests by string name --- obd/OBDResponse.py | 15 +++++++++++++-- obd/codes.py | 24 ++++++++++++------------ tests/test_decoders.py | 8 ++++---- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/obd/OBDResponse.py b/obd/OBDResponse.py index 9619a3e6..f236699b 100644 --- a/obd/OBDResponse.py +++ b/obd/OBDResponse.py @@ -33,6 +33,11 @@ import time from .codes import * +import logging + +logger = logging.getLogger(__name__) + + class OBDResponse(): """ Standard response object for any OBDCommand """ @@ -119,8 +124,14 @@ def __str__(self): def __len__(self): return len(self.tests) - def __getitem__(self, tid): - return self._tests.get(tid, MonitorTest()) + def __getitem__(self, key): + if isinstance(key, int): + return self._tests.get(key, MonitorTest()) + elif isinstance(key, str) or isinstance(key, unicode): + return self.__dict__.get(key, MonitorTest()) + else: + logger.warning("Monitor test results can only be retrieved by TID value or property name") + class MonitorTest(): diff --git a/obd/codes.py b/obd/codes.py index 69922c89..71e5129a 100644 --- a/obd/codes.py +++ b/obd/codes.py @@ -2209,16 +2209,16 @@ TEST_IDS = { # : # 0x0 is reserved - 0x01 : ("rtl_threshold_voltage", "Rich to lean sensor threshold voltage"), - 0x02 : ("ltr_threshold_voltage", "Lean to rich sensor threshold voltage"), - 0x03 : ("low_voltage_switch_time", "Low sensor voltage for switch time calculation"), - 0x04 : ("high_voltage_switch_time", "High sensor voltage for switch time calculation"), - 0x05 : ("rtl_switch_time", "Rich to lean sensor switch time"), - 0x06 : ("ltr_switch_time", "Lean to rich sensor switch time"), - 0x07 : ("min_voltage", "Minimum sensor voltage for test cycle"), - 0x08 : ("max_voltage", "Maximum sensor voltage for test cycle"), - 0x09 : ("transition_time", "Time between sensor transitions"), - 0x0A : ("sensor_period", "Sensor period"), - 0x0B : ("misfire_average", "Average misfire counts for last ten driving cycles"), - 0x0C : ("misfire_count", "Misfire counts for last/current driving cycles"), + 0x01 : ("RTL_THRESHOLD_VOLTAGE", "Rich to lean sensor threshold voltage"), + 0x02 : ("LTR_THRESHOLD_VOLTAGE", "Lean to rich sensor threshold voltage"), + 0x03 : ("LOW_VOLTAGE_SWITCH_TIME", "Low sensor voltage for switch time calculation"), + 0x04 : ("HIGH_VOLTAGE_SWITCH_TIME", "High sensor voltage for switch time calculation"), + 0x05 : ("RTL_SWITCH_TIME", "Rich to lean sensor switch time"), + 0x06 : ("LTR_SWITCH_TIME", "Lean to rich sensor switch time"), + 0x07 : ("MIN_VOLTAGE", "Minimum sensor voltage for test cycle"), + 0x08 : ("MAX_VOLTAGE", "Maximum sensor voltage for test cycle"), + 0x09 : ("TRANSITION_TIME", "Time between sensor transitions"), + 0x0A : ("SENSOR_PERIOD", "Sensor period"), + 0x0B : ("MISFIRE_AVERAGE", "Average misfire counts for last ten driving cycles"), + 0x0C : ("MISFIRE_COUNT", "Misfire counts for last/current driving cycles"), } diff --git a/tests/test_decoders.py b/tests/test_decoders.py index 1735391d..c339f466 100644 --- a/tests/test_decoders.py +++ b/tests/test_decoders.py @@ -188,7 +188,7 @@ def test_monitor(): assert len(v) == 1 # 1 test result # make sure we can look things up by name and TID - assert v[0x01] == v.rtl_threshold_voltage + assert v[0x01] == v.RTL_THRESHOLD_VOLTAGE == v["RTL_THRESHOLD_VOLTAGE"] # make sure we got information assert not v[0x01].is_null() @@ -203,8 +203,8 @@ def test_monitor(): assert len(v) == 3 # 3 test results # make sure we can look things up by name and TID - assert v[0x01] == v.rtl_threshold_voltage - assert v[0x05] == v.rtl_switch_time + assert v[0x01] == v.RTL_THRESHOLD_VOLTAGE == v["RTL_THRESHOLD_VOLTAGE"] + assert v[0x05] == v.RTL_SWITCH_TIME == v["RTL_SWITCH_TIME"] # make sure we got information assert not v[0x01].is_null() @@ -229,7 +229,7 @@ def test_monitor(): assert len(v) == 1 # 1 test result # make sure we can look things up by name and TID - assert v[0x01] == v.rtl_threshold_voltage + assert v[0x01] == v.RTL_THRESHOLD_VOLTAGE == v["RTL_THRESHOLD_VOLTAGE"] # make sure we got information assert not v[0x01].is_null() From ba3c3d4eb4f82d717940a5acae2ca0f67582733e Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Tue, 5 Jul 2016 18:51:01 -0400 Subject: [PATCH 085/128] doc'd mode 06 responses --- docs/Responses.md | 62 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/docs/Responses.md b/docs/Responses.md index c7fa52d1..8277981b 100644 --- a/docs/Responses.md +++ b/docs/Responses.md @@ -115,10 +115,68 @@ response.value[1][2] == True # Bank 1, Sensor 2 is present # Monitors (Mode 06 Responses) -All mode 06 commands return `Monitor` objects holding various test results for the requested sensor. A single monitor response can hold multiple tests. +All mode 06 commands return `Monitor` objects holding various test results for the requested sensor. A single monitor response can hold multiple tests, in the form of `MonitorTest` objects. The OBD standard defines some tests, but vehicles can always implement custom tests beyond the standard. Here are the standard Test IDs (TIDs) that python-OBD will recognize: + +| TID | Name | Description | +|-----|--------------------------|----------------------------------------------------| +| 01 | RTL_THRESHOLD_VOLTAGE | Rich to lean sensor threshold voltage | +| 02 | LTR_THRESHOLD_VOLTAGE | Lean to rich sensor threshold voltage | +| 03 | LOW_VOLTAGE_SWITCH_TIME | Low sensor voltage for switch time calculation | +| 04 | HIGH_VOLTAGE_SWITCH_TIME | High sensor voltage for switch time calculation | +| 05 | RTL_SWITCH_TIME | Rich to lean sensor switch time | +| 06 | LTR_SWITCH_TIME | Lean to rich sensor switch time | +| 07 | MIN_VOLTAGE | Minimum sensor voltage for test cycle | +| 08 | MAX_VOLTAGE | Maximum sensor voltage for test cycle | +| 09 | TRANSITION_TIME | Time between sensor transitions | +| 0A | SENSOR_PERIOD | Sensor period | +| 0B | MISFIRE_AVERAGE | Average misfire counts for last ten driving cycles | +| 0C | MISFIRE_COUNT | Misfire counts for last/current driving cycles | + +Test results can be accessed by property name or TID (same as the `obd.commands` tables). All of the standard tests above will be present, though some may be null. Use the `MonitorTest.is_null()` function to determine if a test is null. ```python -# TODO +response.value.MISFIRE_COUNT + +# OR + +response.value["MISFIRE_COUNT"] + +# OR + +response.value[0x0C] # TID for MISFIRE_COUNT +``` + +All `MonitorTest` objects have the following properties: (for null tests, these are set to `None`) + +```python +result = response.value.MISFIRE_COUNT + +result.tid # integer Test ID for this test +result.name # test name +result.desc # test description +result.value # value of the test (will be a Pint value, or in rare cases, a boolean) +result.min # maximum acceptable value +result.max # minimum acceptable value +result.passed # boolean marking the test as passing +``` + +Here is an example of looking up live misfire counts for the engine's second cylinder: + +```python +import obd + +connection = obd.OBD() + +response = connection.query(obd.commands.MONITOR_MISFIRE_CYLINDER_2) + +# in the test results, lookup the result for MISFIRE_COUNT +result = response.value.MISFIRE_COUNT + +# check that we got data for this test +if not result.is_null(): + print(result.value) # will be a Pint value +else: + print("Misfire count wasn't reported") ``` --- From 30d79f2e404cb5b7164cbdff32730e0d5be92c38 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Tue, 5 Jul 2016 18:53:31 -0400 Subject: [PATCH 086/128] lists are only used on certain DTC commands --- docs/Responses.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Responses.md b/docs/Responses.md index 8277981b..3cdacdcb 100644 --- a/docs/Responses.md +++ b/docs/Responses.md @@ -71,7 +71,7 @@ import obd # Diagnostic Trouble Codes (DTCs) -Each DTC is represented by a tuple containing the DTC code, and a description (if python-OBD has one). When multiple DTCs are returned, they are stored in a list. +Each DTC is represented by a tuple containing the DTC code, and a description (if python-OBD has one). For commands that return multiple DTCs, a list is used. ```python # obd.commands.GET_DTC From 43af43ec7489b19dc708a2f0bc719ec461a254dc Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Tue, 5 Jul 2016 22:29:20 -0400 Subject: [PATCH 087/128] started writing tests for/tweaking status decoder --- docs/Responses.md | 10 ++++++++++ obd/decoders.py | 4 ++-- tests/test_decoders.py | 5 +++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/Responses.md b/docs/Responses.md index 3cdacdcb..396fb267 100644 --- a/docs/Responses.md +++ b/docs/Responses.md @@ -69,6 +69,16 @@ import obd --- +# Status + + + +```python + +``` + +--- + # Diagnostic Trouble Codes (DTCs) Each DTC is represented by a tuple containing the DTC code, and a description (if python-OBD has one). For commands that return multiple DTCs, a list is used. diff --git a/obd/decoders.py b/obd/decoders.py index a0501cd6..dc9667dc 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -247,8 +247,8 @@ def status(messages): bits = bytes_to_bits(d) output = Status() - output.MIL = bitToBool(bits[0]) - output.DTC_count = unbin(bits[1:8]) + output.MIL = bool(d[0] & 0b10000000) + output.DTC_count = d[0] & 0b01111111 output.ignition_type = IGNITION_TYPE[unbin(bits[12])] output.tests.append(Test("Misfire", \ diff --git a/tests/test_decoders.py b/tests/test_decoders.py index c339f466..41aa4063 100644 --- a/tests/test_decoders.py +++ b/tests/test_decoders.py @@ -146,6 +146,11 @@ def test_elm_voltage(): assert d.elm_voltage([ Message([ Frame("12") ]) ]) == 12 * Unit.volt assert d.elm_voltage([ Message([ Frame("12ABCD") ]) ]) == None +def test_status(): + status = d.status(m("83E0FF00")) + assert status.MIL + assert status.DTC_count == 3 + def test_single_dtc(): assert d.single_dtc(m("0104")) == ("P0104", "Mass or Volume Air Flow Circuit Intermittent") assert d.single_dtc(m("4123")) == ("C0123", "") From b3c801fa5aa9b76afa39ff40fbc0b251fc419f53 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 14:01:30 -0400 Subject: [PATCH 088/128] wrote quick and dirty bitarray class --- obd/utils.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/obd/utils.py b/obd/utils.py index 942c2fdc..c75fac5b 100644 --- a/obd/utils.py +++ b/obd/utils.py @@ -50,6 +50,37 @@ class OBDStatus: +class bitarray: + """ + Class for representing bitarrays (inefficiently) + + There's a nice C-optimized lib for this: https://github.com/ilanschnell/bitarray + but python-OBD doesn't use it enough to be worth adding the dependency. + But, if this class starts getting used too much, we should switch to that lib. + """ + + def __init__(self, _bytearray): + bits = "" + + for b in _bytearray[::-1]: # put the bytes in bit-number order + v = bin(b)[2:] + bits += ("0" * (8 - len(v))) + v # pad it with zeros + self.bits = bits[::-1] # reverse, to maintain zero indexing + + def __getitem__(self, key): + if isinstance(key, int): + if key >= 0 and key < len(self.bits): + return self.bits[key] == "1" + else: + return False + elif isinstance(key, slice): + bits = self.bits[key][::-1] # reverse back into correct bit-order + if bits: + return int(bits, 2) + else: + return 0 + + def num_bits_set(n): return bin(n).count("1") From 976775a2cd69294991010ac145a2f9b4c637e182 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 14:05:14 -0400 Subject: [PATCH 089/128] added __str__ for easy debugging --- obd/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/obd/utils.py b/obd/utils.py index c75fac5b..22d7d08b 100644 --- a/obd/utils.py +++ b/obd/utils.py @@ -61,7 +61,6 @@ class bitarray: def __init__(self, _bytearray): bits = "" - for b in _bytearray[::-1]: # put the bytes in bit-number order v = bin(b)[2:] bits += ("0" * (8 - len(v))) + v # pad it with zeros @@ -80,6 +79,9 @@ def __getitem__(self, key): else: return 0 + def __str__(self): + return self.bits[::-1] + def num_bits_set(n): return bin(n).count("1") From 37cb7638a2cbee86ad34a48d30a46498e1739c08 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 14:31:04 -0400 Subject: [PATCH 090/128] using bitarray in PID decoder --- obd/decoders.py | 3 +-- obd/obd.py | 8 +++----- obd/utils.py | 23 +++++++++++++++++------ 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/obd/decoders.py b/obd/decoders.py index dc9667dc..6bd0df31 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -64,8 +64,7 @@ def noop(messages): # hex in, bitstring out def pid(messages): d = messages[0].data - v = bytes_to_bits(d) - return v + return bitarray(d) # returns the raw strings from the ELM def raw_string(messages): diff --git a/obd/obd.py b/obd/obd.py index 97504ebc..654dcf5b 100644 --- a/obd/obd.py +++ b/obd/obd.py @@ -114,11 +114,9 @@ def __load_commands(self): if response.is_null(): continue - supported = response.value # string of binary 01010101010101 - - # loop through PIDs binary - for i in range(len(supported)): - if supported[i] == "1": + # loop through PIDs bitarray + for i, bit in enumerate(response.value): + if bit: mode = get.mode pid = get.pid + i + 1 diff --git a/obd/utils.py b/obd/utils.py index 22d7d08b..5d80edd0 100644 --- a/obd/utils.py +++ b/obd/utils.py @@ -60,11 +60,10 @@ class bitarray: """ def __init__(self, _bytearray): - bits = "" - for b in _bytearray[::-1]: # put the bytes in bit-number order + self.bits = "" + for b in _bytearray: # put the bytes in bit-number order v = bin(b)[2:] - bits += ("0" * (8 - len(v))) + v # pad it with zeros - self.bits = bits[::-1] # reverse, to maintain zero indexing + self.bits += ("0" * (8 - len(v))) + v # pad it with zeros def __getitem__(self, key): if isinstance(key, int): @@ -73,14 +72,26 @@ def __getitem__(self, key): else: return False elif isinstance(key, slice): - bits = self.bits[key][::-1] # reverse back into correct bit-order + bits = self.bits[key] # reverse back into correct bit-order if bits: return int(bits, 2) else: return 0 + def num_set(self): + return self.bits.count("1") + + def num_cleared(self): + return self.bits.count("0") + + def __len__(self): + return len(self.bits) + def __str__(self): - return self.bits[::-1] + return self.bits + + def __iter__(self): + return [ b == "1" for b in self.bits ].__iter__() def num_bits_set(n): From 6331a7f53e5b51a5897e2b36b696ae9a1c6e8f76 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 14:45:40 -0400 Subject: [PATCH 091/128] simplified status decoder --- obd/decoders.py | 53 +++++++++++++++++-------------------------------- 1 file changed, 18 insertions(+), 35 deletions(-) diff --git a/obd/decoders.py b/obd/decoders.py index 6bd0df31..83178a06 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -243,46 +243,29 @@ def elm_voltage(messages): def status(messages): d = messages[0].data - bits = bytes_to_bits(d) + bits = bitarray(d) output = Status() - output.MIL = bool(d[0] & 0b10000000) - output.DTC_count = d[0] & 0b01111111 - output.ignition_type = IGNITION_TYPE[unbin(bits[12])] - - output.tests.append(Test("Misfire", \ - bitToBool(bits[15]), \ - bitToBool(bits[11]))) - - output.tests.append(Test("Fuel System", \ - bitToBool(bits[14]), \ - bitToBool(bits[10]))) - - output.tests.append(Test("Components", \ - bitToBool(bits[13]), \ - bitToBool(bits[9]))) + output.MIL = bits[7] + output.DTC_count = bits[0:7] + output.ignition_type = IGNITION_TYPE[int(bits[12])] + output.tests.append(Test("Misfire", bits[15], bits[11])) + output.tests.append(Test("Fuel System", bits[14], bits[10])) + output.tests.append(Test("Components", bits[13], bits[9])) # different tests for different ignition types - if(output.ignition_type == IGNITION_TYPE[0]): # spark - for i in range(8): - if SPARK_TESTS[i] is not None: - - t = Test(SPARK_TESTS[i], \ - bitToBool(bits[(2 * 8) + i]), \ - bitToBool(bits[(3 * 8) + i])) - - output.tests.append(t) - - elif(output.ignition_type == IGNITION_TYPE[1]): # compression - for i in range(8): - if COMPRESSION_TESTS[i] is not None: - - t = Test(COMPRESSION_TESTS[i], \ - bitToBool(bits[(2 * 8) + i]), \ - bitToBool(bits[(3 * 8) + i])) - - output.tests.append(t) + if bits[12]: # ignition type: compression + for i, name in enumerate(COMPRESSION_TESTS): + t = Test(name, bits[(2 * 8) + i], + bits[(3 * 8) + i]) + output.tests.append(t) + + else: # ignition type: spark + for i, name in enumerate(SPARK_TESTS): + t = Test(name, bits[(2 * 8) + i], + bits[(3 * 8) + i]) + output.tests.append(t) return output From 0cc539fffc0409c1fc57c0bd2a3493ca139c1ec3 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 17:14:48 -0400 Subject: [PATCH 092/128] reimplemented status, wrote test --- obd/OBDResponse.py | 21 ++++++++----- obd/codes.py | 42 +++++++++++++++----------- obd/decoders.py | 46 +++++++++++++++++----------- obd/utils.py | 4 +-- tests/test_decoders.py | 68 ++++++++++++++++++++++++++++++++++++++---- 5 files changed, 131 insertions(+), 50 deletions(-) diff --git a/obd/OBDResponse.py b/obd/OBDResponse.py index f236699b..e3c7d550 100644 --- a/obd/OBDResponse.py +++ b/obd/OBDResponse.py @@ -77,18 +77,25 @@ def __init__(self): self.MIL = False self.DTC_count = 0 self.ignition_type = "" - self.tests = [] + + # make sure each test is available by name + # until real data comes it. This also prevents things from + # breaking when the user looks up a standard test that's null. + null_test = StatusTest() + for name in BASE_TESTS + SPARK_TESTS + COMPRESSION_TESTS: + if name: # filter out None/reserved tests + self.__dict__[name] = null_test -class Test(): - def __init__(self, name, available, incomplete): - self.name = name - self.available = available - self.incomplete = incomplete +class StatusTest(): + def __init__(self, name="", available=False, complete=False): + self.name = name + self.available = available + self.complete = complete def __str__(self): a = "Available" if self.available else "Unavailable" - c = "Incomplete" if self.incomplete else "Complete" + c = "Complete" if self.complete else "Incomplete" return "Test %s: %s, %s" % (self.name, a, c) diff --git a/obd/codes.py b/obd/codes.py index 71e5129a..97202e92 100644 --- a/obd/codes.py +++ b/obd/codes.py @@ -2101,30 +2101,36 @@ } IGNITION_TYPE = [ - "Spark", - "Compression", + "spark", + "compression", +] + +BASE_TESTS = [ + "MISFIRE_MONITORING", + "FUEL_SYSTEM_MONITORING", + "COMPONENT_MONITORING", ] SPARK_TESTS = [ - "EGR System", - "Oxygen Sensor Heater", - "Oxygen Sensor", - "A/C Refrigerant", - "Secondary Air System", - "Evaporative System", - "Heated Catalyst", - "Catalyst", + "CATALYST_MONITORING", + "HEATED_CATALYST_MONITORING", + "EVAPORATIVE_SYSTEM_MONITORING", + "SECONDARY_AIR_SYSTEM_MONITORING", + None, + "OXYGEN_SENSOR_MONITORING", + "OXYGEN_SENSOR_HEATER_MONITORING", + "EGR_VVT_SYSTEM_MONITORING" ] COMPRESSION_TESTS = [ - "EGR and/or VVT System", - "PM filter monitoring", - "Exhaust Gas Sensor", - "None", - "Boost Pressure", - "None", - "NOx/SCR Monitor", - "NMHC Catalyst", + "NMHC_CATALYST_MONITORING", + "NOX_SCR_AFTERTREATMENT_MONITORING", + None, + "BOOST_PRESSURE_MONITORING", + None, + "EXHAUST_GAS_SENSOR_MONITORING", + "PM_FILTER_MONITORING", + "EGR_VVT_SYSTEM_MONITORING", ] FUEL_STATUS = [ diff --git a/obd/decoders.py b/obd/decoders.py index 83178a06..5edb6344 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -33,7 +33,7 @@ import functools from .utils import * from .codes import * -from .OBDResponse import Status, Test, Monitor, MonitorTest +from .OBDResponse import Status, StatusTest, Monitor, MonitorTest from .UnitsAndScaling import Unit, UAS_IDS import logging @@ -245,27 +245,39 @@ def status(messages): d = messages[0].data bits = bitarray(d) + # ┌Components not ready + # |┌Fuel not ready + # ||┌Misfire not ready + # |||┌Spark vs. Compression + # ||||┌Components supported + # |||||┌Fuel supported + # ┌MIL ||||||┌Misfire supported + # | ||||||| + # 10000011 00000111 11111111 00000000 + # [# DTC] X [supprt] [~ready] + output = Status() - output.MIL = bits[7] - output.DTC_count = bits[0:7] + output.MIL = bits[0] + output.DTC_count = bits[1:8] output.ignition_type = IGNITION_TYPE[int(bits[12])] - output.tests.append(Test("Misfire", bits[15], bits[11])) - output.tests.append(Test("Fuel System", bits[14], bits[10])) - output.tests.append(Test("Components", bits[13], bits[9])) + # load the 3 base tests that are always present + for i, name in enumerate(BASE_TESTS[::-1]): + t = StatusTest(name, bits[13 + i], not bits[9 + i]) + output.__dict__[name] = t # different tests for different ignition types - if bits[12]: # ignition type: compression - for i, name in enumerate(COMPRESSION_TESTS): - t = Test(name, bits[(2 * 8) + i], - bits[(3 * 8) + i]) - output.tests.append(t) - - else: # ignition type: spark - for i, name in enumerate(SPARK_TESTS): - t = Test(name, bits[(2 * 8) + i], - bits[(3 * 8) + i]) - output.tests.append(t) + if bits[12]: # compression + for i, name in enumerate(COMPRESSION_TESTS[::-1]): # reverse to correct for bit vs. indexing order + t = StatusTest(name, bits[(2 * 8) + i], + not bits[(3 * 8) + i]) + output.__dict__[name] = t + + else: # spark + for i, name in enumerate(SPARK_TESTS[::-1]): # reverse to correct for bit vs. indexing order + t = StatusTest(name, bits[(2 * 8) + i], + not bits[(3 * 8) + i]) + output.__dict__[name] = t return output diff --git a/obd/utils.py b/obd/utils.py index 5d80edd0..5e576f88 100644 --- a/obd/utils.py +++ b/obd/utils.py @@ -61,7 +61,7 @@ class bitarray: def __init__(self, _bytearray): self.bits = "" - for b in _bytearray: # put the bytes in bit-number order + for b in _bytearray: v = bin(b)[2:] self.bits += ("0" * (8 - len(v))) + v # pad it with zeros @@ -72,7 +72,7 @@ def __getitem__(self, key): else: return False elif isinstance(key, slice): - bits = self.bits[key] # reverse back into correct bit-order + bits = self.bits[key] if bits: return int(bits, 2) else: diff --git a/tests/test_decoders.py b/tests/test_decoders.py index 41aa4063..da955a06 100644 --- a/tests/test_decoders.py +++ b/tests/test_decoders.py @@ -3,7 +3,7 @@ from obd.UnitsAndScaling import Unit from obd.protocols.protocol import Frame, Message -from obd.codes import TEST_IDS +from obd.codes import BASE_TESTS, COMPRESSION_TESTS, SPARK_TESTS, TEST_IDS import obd.decoders as d @@ -41,9 +41,9 @@ def test_raw_string(): assert d.raw_string([ Message([ Frame("A") ]), Message([ Frame("B") ]) ]) == "A\nB" def test_pid(): - assert d.pid(m("00000000")) == "00000000000000000000000000000000" - assert d.pid(m("F00AA00F")) == "11110000000010101010000000001111" - assert d.pid(m("11")) == "00010001" + assert d.pid(m("00000000")).bits == "00000000000000000000000000000000" + assert d.pid(m("F00AA00F")).bits == "11110000000010101010000000001111" + assert d.pid(m("11")).bits == "00010001" def test_percent(): assert d.percent(m("00")) == 0.0 * Unit.percent @@ -147,13 +147,69 @@ def test_elm_voltage(): assert d.elm_voltage([ Message([ Frame("12ABCD") ]) ]) == None def test_status(): - status = d.status(m("83E0FF00")) + status = d.status(m("8307FF00")) assert status.MIL assert status.DTC_count == 3 + assert status.ignition_type == "spark" + + for name in BASE_TESTS: + assert status.__dict__[name].available + assert status.__dict__[name].complete + + # check that NONE of the compression tests are available + for name in COMPRESSION_TESTS: + if name and name not in SPARK_TESTS: # there's one test name in common between spark/compression + assert not status.__dict__[name].available + assert not status.__dict__[name].complete + + # check that ALL of the spark tests are availablex + for name in SPARK_TESTS: + if name: + assert status.__dict__[name].available + assert status.__dict__[name].complete + + # a different test + status = d.status(m("00790303")) + assert not status.MIL + assert status.DTC_count == 0 + assert status.ignition_type == "compression" + + # availability + assert status.MISFIRE_MONITORING.available + assert not status.FUEL_SYSTEM_MONITORING.available + assert not status.COMPONENT_MONITORING.available + + # completion + assert not status.MISFIRE_MONITORING.complete + assert not status.FUEL_SYSTEM_MONITORING.complete + assert not status.COMPONENT_MONITORING.complete + + # check that NONE of the spark tests are availablex + for name in SPARK_TESTS: + if name and name not in COMPRESSION_TESTS: + assert not status.__dict__[name].available + assert not status.__dict__[name].complete + + # availability + assert status.NMHC_CATALYST_MONITORING.available + assert status.NOX_SCR_AFTERTREATMENT_MONITORING.available + assert not status.BOOST_PRESSURE_MONITORING.available + assert not status.EXHAUST_GAS_SENSOR_MONITORING.available + assert not status.PM_FILTER_MONITORING.available + assert not status.EGR_VVT_SYSTEM_MONITORING.available + + # completion + assert not status.NMHC_CATALYST_MONITORING.complete + assert not status.NOX_SCR_AFTERTREATMENT_MONITORING.complete + assert status.BOOST_PRESSURE_MONITORING.complete + assert status.EXHAUST_GAS_SENSOR_MONITORING.complete + assert status.PM_FILTER_MONITORING.complete + assert status.EGR_VVT_SYSTEM_MONITORING.complete + def test_single_dtc(): assert d.single_dtc(m("0104")) == ("P0104", "Mass or Volume Air Flow Circuit Intermittent") - assert d.single_dtc(m("4123")) == ("C0123", "") + assert d.single_dtc(m("4123")) == ("C0123", "") # reverse back into correct bit-order assert d.single_dtc(m("01")) == None assert d.single_dtc(m("010400")) == None From 5d1c5024e94d15db2821e57678e4563e29898813 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 17:32:20 -0400 Subject: [PATCH 093/128] wrote docs for status command --- docs/Responses.md | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/docs/Responses.md b/docs/Responses.md index 396fb267..63e93fdf 100644 --- a/docs/Responses.md +++ b/docs/Responses.md @@ -71,12 +71,42 @@ import obd # Status - +The status command returns information about the Malfunction Indicator Light (check-engine light), the number of trouble codes being thrown, and the type of engine. ```python +response.value.MIL # boolean for whether the check-engine is lit +response.value.DTC_count # number (int) of DTCs being thrown +responce.value.ignition_type # "spark" or "compression" +``` + +The status command also provides information regarding the availability and status of various system tests. These are exposed as `StatusTest` objects, loaded into named properties. Each test object has boolean flags for its availability and completion. +```python +response.value.MISFIRE_MONITORING.available # boolean for test availability +response.value.MISFIRE_MONITORING.complete # boolean for test completion ``` +Here are all of the tests names that python-OBD reports: + +| Tests | +|-----------------------------------| +| MISFIRE_MONITORING | +| FUEL_SYSTEM_MONITORING | +| COMPONENT_MONITORING | +| CATALYST_MONITORING | +| HEATED_CATALYST_MONITORING | +| EVAPORATIVE_SYSTEM_MONITORING | +| SECONDARY_AIR_SYSTEM_MONITORING | +| OXYGEN_SENSOR_MONITORING | +| OXYGEN_SENSOR_HEATER_MONITORING | +| EGR_VVT_SYSTEM_MONITORING | +| NMHC_CATALYST_MONITORING | +| NOX_SCR_AFTERTREATMENT_MONITORING | +| BOOST_PRESSURE_MONITORING | +| EXHAUST_GAS_SENSOR_MONITORING | +| PM_FILTER_MONITORING | + + --- # Diagnostic Trouble Codes (DTCs) From de288cbf03b268e74871d6a3b3f80e6b6f0b0e84 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 17:33:29 -0400 Subject: [PATCH 094/128] link from commands table to status response description --- docs/Commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Commands.md b/docs/Commands.md index bd39dd4f..952829ba 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -75,7 +75,7 @@ obd.commands.has_pid(1, 12) # True |PID | Name | Description | Response Value | |----|---------------------------|-----------------------------------------|-----------------------| | 00 | PIDS_A | Supported PIDs [01-20] | bitstring | -| 01 | STATUS | Status since DTCs cleared | | +| 01 | STATUS | Status since DTCs cleared | [special](Responses.md#status) | | 02 | FREEZE_DTC | DTC that triggered the freeze frame | [special](Responses.md#diagnostic-trouble-codes-dtcs) | | 03 | FUEL_STATUS | Fuel System Status | string | | 04 | ENGINE_LOAD | Calculated Engine Load | Unit.percent | From 44c1896bd7874d9b4177d17d20f8e9fc4bb390c5 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 17:52:13 -0400 Subject: [PATCH 095/128] removed old bit utils --- obd/decoders.py | 18 +++++++++--------- obd/utils.py | 21 +++++++++------------ 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/obd/decoders.py b/obd/decoders.py index 5edb6344..156a9001 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -199,11 +199,11 @@ def fuel_rate(messages): # special bit encoding for PID 13 def o2_sensors(messages): d = messages[0].data - bitstring = bytes_to_bits(d) + bits = bitarray(d) return ( (), # bank 0 is invalid - tuple([ b == "1" for b in bitstring[:4] ]), # bank 1 - tuple([ b == "1" for b in bitstring[4:] ]), # bank 2 + tuple(bits[:4]), # bank 1 + tuple(bits[4:]), # bank 2 ) def aux_input_status(messages): @@ -213,13 +213,13 @@ def aux_input_status(messages): # special bit encoding for PID 1D def o2_sensors_alt(messages): d = messages[0].data - bitstring = bytes_to_bits(d) + bits = bitarray(d) return ( (), # bank 0 is invalid - tuple([ b == "1" for b in bitstring[:2] ]), # bank 1 - tuple([ b == "1" for b in bitstring[2:4] ]), # bank 2 - tuple([ b == "1" for b in bitstring[4:6] ]), # bank 3 - tuple([ b == "1" for b in bitstring[6:] ]), # bank 4 + tuple(bits[:2]), # bank 1 + tuple(bits[2:4]), # bank 2 + tuple(bits[4:6]), # bank 3 + tuple(bits[6:]), # bank 4 ) def elm_voltage(messages): @@ -258,7 +258,7 @@ def status(messages): output = Status() output.MIL = bits[0] - output.DTC_count = bits[1:8] + output.DTC_count = bits.value(1, 8) output.ignition_type = IGNITION_TYPE[int(bits[12])] # load the 3 base tests that are always present diff --git a/obd/utils.py b/obd/utils.py index 5e576f88..73ba151b 100644 --- a/obd/utils.py +++ b/obd/utils.py @@ -74,9 +74,9 @@ def __getitem__(self, key): elif isinstance(key, slice): bits = self.bits[key] if bits: - return int(bits, 2) + return [ b == "1" for b in bits ] else: - return 0 + return [] def num_set(self): return self.bits.count("1") @@ -84,6 +84,13 @@ def num_set(self): def num_cleared(self): return self.bits.count("0") + def value(self, start, stop): + bits = self.bits[start:stop] + if bits: + return int(bits, 2) + else: + return 0 + def __len__(self): return len(self.bits) @@ -97,9 +104,6 @@ def __iter__(self): def num_bits_set(n): return bin(n).count("1") -def unbin(_bin): - return int(_bin, 2) - def bytes_to_int(bs): """ converts a big-endian byte array into a single integer """ v = 0 @@ -109,13 +113,6 @@ def bytes_to_int(bs): p += 8 return v -def bytes_to_bits(bs): - bits = "" - for b in bs: - v = bin(b)[2:] - bits += ("0" * (8 - len(v))) + v # pad it with zeros - return bits - def bytes_to_hex(bs): h = "" for b in bs: From 69fc0424ddf67ebeb2333ddb4ef3b501f81f829b Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 18:08:12 -0400 Subject: [PATCH 096/128] rewrote fuel status, using bitarrays, read second system --- obd/decoders.py | 28 ++++++++++++++-------------- tests/test_decoders.py | 8 ++++++-- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/obd/decoders.py b/obd/decoders.py index 156a9001..64e7d87c 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -285,25 +285,25 @@ def status(messages): def fuel_status(messages): d = messages[0].data - v = d[0] # todo, support second fuel system - - if v <= 0: - logger.debug("Invalid fuel status response (v <= 0)") - return None + bits = bitarray(d) - i = math.log(v, 2) # only a single bit should be on + status_1 = "" + status_2 = "" - if i % 1 != 0: - logger.debug("Invalid fuel status response (multiple bits set)") - return None + if bits[0:8].count(True) == 1: + status_1 = FUEL_STATUS[ 7 - bits[0:8].index(True) ] + else: + logger.debug("Invalid response for fuel status (multiple/no bits set)") - i = int(i) + if bits[8:16].count(True) == 1: + status_2 = FUEL_STATUS[ 7 - bits[8:16].index(True) ] + else: + logger.debug("Invalid response for fuel status (multiple/no bits set)") - if i >= len(FUEL_STATUS): - logger.debug("Invalid fuel status response (no table entry)") + if not status_1 and not status_2: return None - - return FUEL_STATUS[i] + else: + return (status_1, status_2) def air_status(messages): diff --git a/tests/test_decoders.py b/tests/test_decoders.py index da955a06..6d33f1d0 100644 --- a/tests/test_decoders.py +++ b/tests/test_decoders.py @@ -115,9 +115,13 @@ def test_fuel_rate(): assert d.fuel_rate(m("FFFF")) == 3276.75 * Unit.liters_per_hour def test_fuel_status(): - assert d.fuel_status(m("0100")) == "Open loop due to insufficient engine temperature" - assert d.fuel_status(m("0800")) == "Open loop due to system failure" + assert d.fuel_status(m("0100")) == ("Open loop due to insufficient engine temperature", "") + assert d.fuel_status(m("0800")) == ("Open loop due to system failure", "") + assert d.fuel_status(m("0808")) == ("Open loop due to system failure", + "Open loop due to system failure") + assert d.fuel_status(m("0000")) == None assert d.fuel_status(m("0300")) == None + assert d.fuel_status(m("0303")) == None def test_air_status(): assert d.air_status(m("01")) == "Upstream" From 7d039a36913d4c2ad479f1900eb8a2d7e96bf97d Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 18:26:25 -0400 Subject: [PATCH 097/128] added fuel and air status links --- docs/Commands.md | 22 +++++++++++----------- docs/Responses.md | 28 ++++++++++++++++++++++++++++ tests/test_decoders.py | 1 + 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/docs/Commands.md b/docs/Commands.md index 952829ba..fbd54eae 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -74,10 +74,10 @@ obd.commands.has_pid(1, 12) # True |PID | Name | Description | Response Value | |----|---------------------------|-----------------------------------------|-----------------------| -| 00 | PIDS_A | Supported PIDs [01-20] | bitstring | +| 00 | PIDS_A | Supported PIDs [01-20] | bitarray | | 01 | STATUS | Status since DTCs cleared | [special](Responses.md#status) | | 02 | FREEZE_DTC | DTC that triggered the freeze frame | [special](Responses.md#diagnostic-trouble-codes-dtcs) | -| 03 | FUEL_STATUS | Fuel System Status | string | +| 03 | FUEL_STATUS | Fuel System Status | [(string, string)](Responses.md#fuel-status) | | 04 | ENGINE_LOAD | Calculated Engine Load | Unit.percent | | 05 | COOLANT_TEMP | Engine Coolant Temperature | Unit.celsius | | 06 | SHORT_FUEL_TRIM_1 | Short Term Fuel Trim - Bank 1 | Unit.percent | @@ -92,7 +92,7 @@ obd.commands.has_pid(1, 12) # True | 0F | INTAKE_TEMP | Intake Air Temp | Unit.celsius | | 10 | MAF | Air Flow Rate (MAF) | Unit.grams_per_second | | 11 | THROTTLE_POS | Throttle Position | Unit.percent | -| 12 | AIR_STATUS | Secondary Air Status | string | +| 12 | AIR_STATUS | Secondary Air Status | [string](Responses.md#air-status) | | 13 | O2_SENSORS | O2 Sensors Present | [special](Responses.md#oxygen-sensors-present) | | 14 | O2_B1S1 | O2: Bank 1 - Sensor 1 Voltage | Unit.volt | | 15 | O2_B1S2 | O2: Bank 1 - Sensor 2 Voltage | Unit.volt | @@ -106,7 +106,7 @@ obd.commands.has_pid(1, 12) # True | 1D | O2_SENSORS_ALT | O2 Sensors Present (alternate) | [special](Responses.md#oxygen-sensors-present) | | 1E | AUX_INPUT_STATUS | Auxiliary input status (power take off) | boolean | | 1F | RUN_TIME | Engine Run Time | Unit.second | -| 20 | PIDS_B | Supported PIDs [21-40] | bitstring | +| 20 | PIDS_B | Supported PIDs [21-40] | bitarray | | 21 | DISTANCE_W_MIL | Distance Traveled with MIL on | Unit.kilometer | | 22 | FUEL_RAIL_PRESSURE_VAC | Fuel Rail Pressure (relative to vacuum) | Unit.kilopascal | | 23 | FUEL_RAIL_PRESSURE_DIRECT | Fuel Rail Pressure (direct inject) | Unit.kilopascal | @@ -138,7 +138,7 @@ obd.commands.has_pid(1, 12) # True | 3D | CATALYST_TEMP_B2S1 | Catalyst Temperature: Bank 2 - Sensor 1 | Unit.celsius | | 3E | CATALYST_TEMP_B1S2 | Catalyst Temperature: Bank 1 - Sensor 2 | Unit.celsius | | 3F | CATALYST_TEMP_B2S2 | Catalyst Temperature: Bank 2 - Sensor 2 | Unit.celsius | -| 40 | PIDS_C | Supported PIDs [41-60] | bitstring | +| 40 | PIDS_C | Supported PIDs [41-60] | bitarray | | 41 | *unsupported* | *unsupported* | | | 42 | *unsupported* | *unsupported* | | | 43 | *unsupported* | *unsupported* | | @@ -212,7 +212,7 @@ Mode 06 commands are used to monitor various test results from the vehicle. All |PID | Name | Description | Response Value | |-------|-----------------------------|--------------------------------------------|-----------------------| -| 00 | MIDS_A | Supported MIDs [01-20] | bitstring | +| 00 | MIDS_A | Supported MIDs [01-20] | bitarray | | 01 | MONITOR_O2_B1S1 | O2 Sensor Monitor Bank 1 - Sensor 1 | [monitor](Responses.md#monitors-mode-06-responses) | | 02 | MONITOR_O2_B1S2 | O2 Sensor Monitor Bank 1 - Sensor 2 | [monitor](Responses.md#monitors-mode-06-responses) | | 03 | MONITOR_O2_B1S3 | O2 Sensor Monitor Bank 1 - Sensor 3 | [monitor](Responses.md#monitors-mode-06-responses) | @@ -230,7 +230,7 @@ Mode 06 commands are used to monitor various test results from the vehicle. All | 0F | MONITOR_O2_B4S3 | O2 Sensor Monitor Bank 4 - Sensor 3 | [monitor](Responses.md#monitors-mode-06-responses) | | 10 | MONITOR_O2_B4S4 | O2 Sensor Monitor Bank 4 - Sensor 4 | [monitor](Responses.md#monitors-mode-06-responses) | | *gap* | | | -| 20 | MIDS_B | Supported MIDs [21-40] | bitstring | +| 20 | MIDS_B | Supported MIDs [21-40] | bitarray | | 21 | MONITOR_CATALYST_B1 | Catalyst Monitor Bank 1 | [monitor](Responses.md#monitors-mode-06-responses) | | 22 | MONITOR_CATALYST_B2 | Catalyst Monitor Bank 2 | [monitor](Responses.md#monitors-mode-06-responses) | | 23 | MONITOR_CATALYST_B3 | Catalyst Monitor Bank 3 | [monitor](Responses.md#monitors-mode-06-responses) | @@ -250,7 +250,7 @@ Mode 06 commands are used to monitor various test results from the vehicle. All | 3C | MONITOR_EVAP_020 | EVAP Monitor (0.020\") | [monitor](Responses.md#monitors-mode-06-responses) | | 3D | MONITOR_PURGE_FLOW | Purge Flow Monitor | [monitor](Responses.md#monitors-mode-06-responses) | | *gap* | | | -| 40 | MIDS_C | Supported MIDs [41-60] | bitstring | +| 40 | MIDS_C | Supported MIDs [41-60] | bitarray | | 41 | MONITOR_O2_HEATER_B1S1 | O2 Sensor Heater Monitor Bank 1 - Sensor 1 | [monitor](Responses.md#monitors-mode-06-responses) | | 42 | MONITOR_O2_HEATER_B1S2 | O2 Sensor Heater Monitor Bank 1 - Sensor 2 | [monitor](Responses.md#monitors-mode-06-responses) | | 43 | MONITOR_O2_HEATER_B1S3 | O2 Sensor Heater Monitor Bank 1 - Sensor 3 | [monitor](Responses.md#monitors-mode-06-responses) | @@ -268,7 +268,7 @@ Mode 06 commands are used to monitor various test results from the vehicle. All | 4F | MONITOR_O2_HEATER_B4S3 | O2 Sensor Heater Monitor Bank 4 - Sensor 3 | [monitor](Responses.md#monitors-mode-06-responses) | | 50 | MONITOR_O2_HEATER_B4S4 | O2 Sensor Heater Monitor Bank 4 - Sensor 4 | [monitor](Responses.md#monitors-mode-06-responses) | | *gap* | | | -| 60 | MIDS_D | Supported MIDs [61-80] | bitstring | +| 60 | MIDS_D | Supported MIDs [61-80] | bitarray | | 61 | MONITOR_HEATED_CATALYST_B1 | Heated Catalyst Monitor Bank 1 | [monitor](Responses.md#monitors-mode-06-responses) | | 62 | MONITOR_HEATED_CATALYST_B2 | Heated Catalyst Monitor Bank 2 | [monitor](Responses.md#monitors-mode-06-responses) | | 63 | MONITOR_HEATED_CATALYST_B3 | Heated Catalyst Monitor Bank 3 | [monitor](Responses.md#monitors-mode-06-responses) | @@ -279,7 +279,7 @@ Mode 06 commands are used to monitor various test results from the vehicle. All | 73 | MONITOR_SECONDARY_AIR_3 | Secondary Air Monitor 3 | [monitor](Responses.md#monitors-mode-06-responses) | | 74 | MONITOR_SECONDARY_AIR_4 | Secondary Air Monitor 4 | [monitor](Responses.md#monitors-mode-06-responses) | | *gap* | | | -| 80 | MIDS_E | Supported MIDs [81-A0] | bitstring | +| 80 | MIDS_E | Supported MIDs [81-A0] | bitarray | | 81 | MONITOR_FUEL_SYSTEM_B1 | Fuel System Monitor Bank 1 | [monitor](Responses.md#monitors-mode-06-responses) | | 82 | MONITOR_FUEL_SYSTEM_B2 | Fuel System Monitor Bank 2 | [monitor](Responses.md#monitors-mode-06-responses) | | 83 | MONITOR_FUEL_SYSTEM_B3 | Fuel System Monitor Bank 3 | [monitor](Responses.md#monitors-mode-06-responses) | @@ -293,7 +293,7 @@ Mode 06 commands are used to monitor various test results from the vehicle. All | 98 | MONITOR_NOX_CATALYST_B1 | NOx Catalyst Monitor Bank 1 | [monitor](Responses.md#monitors-mode-06-responses) | | 99 | MONITOR_NOX_CATALYST_B2 | NOx Catalyst Monitor Bank 2 | [monitor](Responses.md#monitors-mode-06-responses) | | *gap* | | | -| A0 | MIDS_F | Supported MIDs [A1-C0] | bitstring | +| A0 | MIDS_F | Supported MIDs [A1-C0] | bitarray | | A1 | MONITOR_MISFIRE_GENERAL | Misfire Monitor General Data | [monitor](Responses.md#monitors-mode-06-responses) | | A2 | MONITOR_MISFIRE_CYLINDER_1 | Misfire Cylinder 1 Data | [monitor](Responses.md#monitors-mode-06-responses) | | A3 | MONITOR_MISFIRE_CYLINDER_2 | Misfire Cylinder 2 Data | [monitor](Responses.md#monitors-mode-06-responses) | diff --git a/docs/Responses.md b/docs/Responses.md index 63e93fdf..0048d8fe 100644 --- a/docs/Responses.md +++ b/docs/Responses.md @@ -127,6 +127,34 @@ responce.value = ("P0104", "Mass or Volume Air Flow Circuit Intermittent") --- +# Fuel Status + +The fuel status is a tuple of two strings, telling the status of the first and second fuel systems. Most cars only have one system, so the second element will likely be an empty string. The possible fuel statuses are: + +| Fuel Status | +| ----------------------------------------------------------------------------------------------| +| `""` | +| `"Open loop due to insufficient engine temperature"` | +| `"Closed loop, using oxygen sensor feedback to determine fuel mix"` | +| `"Open loop due to engine load OR fuel cut due to deceleration"` | +| `"Open loop due to system failure"` | +| `"Closed loop, using at least one oxygen sensor but there is a fault in the feedback system"` | + +--- + +# Air Status + +The air status will be one of these strings: + +| Air Status | +| ---------------------------------------| +| `"Upstream"` | +| `"Downstream of catalytic converter"` | +| `"From the outside atmosphere or off"` | +| `"Pump commanded on for diagnostics"` | + +--- + # Oxygen Sensors Present Returns a 2D structure of tuples (representing bank and sensor number), that holds boolean values for sensor presence. diff --git a/tests/test_decoders.py b/tests/test_decoders.py index 6d33f1d0..8c5e6977 100644 --- a/tests/test_decoders.py +++ b/tests/test_decoders.py @@ -119,6 +119,7 @@ def test_fuel_status(): assert d.fuel_status(m("0800")) == ("Open loop due to system failure", "") assert d.fuel_status(m("0808")) == ("Open loop due to system failure", "Open loop due to system failure") + assert d.fuel_status(m("0008")) == ("", "Open loop due to system failure") assert d.fuel_status(m("0000")) == None assert d.fuel_status(m("0300")) == None assert d.fuel_status(m("0303")) == None From 669827d688e3cb9b02756d7aea6988bd21337632 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 18:28:42 -0400 Subject: [PATCH 098/128] simplified air status decoder with bitarray --- obd/decoders.py | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/obd/decoders.py b/obd/decoders.py index 64e7d87c..f12ff41a 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -308,25 +308,15 @@ def fuel_status(messages): def air_status(messages): d = messages[0].data - v = d[0] - - if v <= 0: - logger.debug("Invalid air status response (v <= 0)") - return None - - i = math.log(v, 2) # only a single bit should be on - - if i % 1 != 0: - logger.debug("Invalid air status response (multiple bits set)") - return None - - i = int(i) + bits = bitarray(d) - if i >= len(AIR_STATUS): - logger.debug("Invalid air status response (no table entry)") - return None + status = None + if bits.num_set() == 1: + status = AIR_STATUS[ 7 - bits[0:8].index(True) ] + else: + logger.debug("Invalid response for fuel status (multiple/no bits set)") - return AIR_STATUS[i] + return status def obd_compliance(_hex): From e2676919a46b6ee3cdf9b5438f9b46a1d02b0216 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 18:34:42 -0400 Subject: [PATCH 099/128] removed lingering bit-handling functions --- obd/protocols/protocol.py | 4 ++-- obd/utils.py | 6 ------ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/obd/protocols/protocol.py b/obd/protocols/protocol.py index d0c102ec..2dce7383 100644 --- a/obd/protocols/protocol.py +++ b/obd/protocols/protocol.py @@ -30,7 +30,7 @@ ######################################################################## from binascii import hexlify -from obd.utils import isHex, num_bits_set +from obd.utils import isHex, bitarray import logging @@ -267,7 +267,7 @@ def populate_ecu_map(self, messages): tx_id = None for message in messages: - bits = sum([num_bits_set(b) for b in message.data]) + bits = bitarray(message.data).num_set() if bits > best: best = bits diff --git a/obd/utils.py b/obd/utils.py index 73ba151b..b1061693 100644 --- a/obd/utils.py +++ b/obd/utils.py @@ -101,9 +101,6 @@ def __iter__(self): return [ b == "1" for b in self.bits ].__iter__() -def num_bits_set(n): - return bin(n).count("1") - def bytes_to_int(bs): """ converts a big-endian byte array into a single integer """ v = 0 @@ -120,9 +117,6 @@ def bytes_to_hex(bs): h += ("0" * (2 - len(bh))) + bh return h -def bitToBool(_bit): - return (_bit == '1') - def twos_comp(val, num_bits): """compute the 2's compliment of int value val""" if( (val&(1<<(num_bits-1))) != 0 ): From 1a54674cbbf01467a3043e63263fe70ab658b89d Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 18:39:32 -0400 Subject: [PATCH 100/128] removed old reference to OBDResponse.unit --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 2b1583f6..7fff3bba 100644 --- a/docs/index.md +++ b/docs/index.md @@ -35,7 +35,7 @@ print(response.value) # returns unit-bearing values thanks to Pint print(response.value.magnitude) # or simple floats ``` -OBD connections operate in a request-reply fashion. To retrieve data from the car, you must send commands that query for the data you want (e.g. RPM, Vehicle speed, etc). In python-OBD, this is done with the `query()` function. The commands themselves are represented as objects, and can be looked up by name or value in `obd.commands`. The `query()` function will return a response object with parsed data in its `value` and `unit` properties. +OBD connections operate in a request-reply fashion. To retrieve data from the car, you must send commands that query for the data you want (e.g. RPM, Vehicle speed, etc). In python-OBD, this is done with the `query()` function. The commands themselves are represented as objects, and can be looked up by name or value in `obd.commands`. The `query()` function will return a response object with parsed data in its `value` property.
From b90d27475883d3219f8c7b1a2d3523cfbd4035ba Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 18:40:25 -0400 Subject: [PATCH 101/128] responce --> response --- docs/Responses.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/Responses.md b/docs/Responses.md index 0048d8fe..6e4f37ce 100644 --- a/docs/Responses.md +++ b/docs/Responses.md @@ -76,7 +76,7 @@ The status command returns information about the Malfunction Indicator Light (ch ```python response.value.MIL # boolean for whether the check-engine is lit response.value.DTC_count # number (int) of DTCs being thrown -responce.value.ignition_type # "spark" or "compression" +response.value.ignition_type # "spark" or "compression" ``` The status command also provides information regarding the availability and status of various system tests. These are exposed as `StatusTest` objects, loaded into named properties. Each test object has boolean flags for its availability and completion. @@ -115,14 +115,14 @@ Each DTC is represented by a tuple containing the DTC code, and a description (i ```python # obd.commands.GET_DTC -responce.value = [ +response.value = [ ("P0104", "Mass or Volume Air Flow Circuit Intermittent"), ("B0003", ""), # unknown error code, it's probably vehicle-specific ("C0123", "") ] # obd.commands.FREEZE_DTC -responce.value = ("P0104", "Mass or Volume Air Flow Circuit Intermittent") +response.value = ("P0104", "Mass or Volume Air Flow Circuit Intermittent") ``` --- @@ -161,14 +161,14 @@ Returns a 2D structure of tuples (representing bank and sensor number), that hol ```python # obd.commands.O2_SENSORS -responce.value = ( +response.value = ( (), # bank 0 is invalid, this is merely for correct indexing (True, True, True, False), # bank 1 (False, False, False, False) # bank 2 ) # obd.commands.O2_SENSORS_ALT -responce.value = ( +response.value = ( (), # bank 0 is invalid, this is merely for correct indexing (True, True), # bank 1 (True, False), # bank 2 From a81c5b22248c1b11acec1c9db78370201d4c2edb Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 18:41:37 -0400 Subject: [PATCH 102/128] reorganized module layout --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 7fff3bba..3ad138b3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -48,11 +48,11 @@ obd.OBD # main OBD connection class obd.Async # asynchronous OBD connection class obd.commands # command tables obd.Unit # unit tables (a Pint UnitRegistry) -obd.logger # the OBD module's root logger (for debug) obd.OBDStatus # enum for connection status obd.scan_serial # util function for manually scanning for OBD adapters obd.OBDCommand # class for making your own OBD Commands obd.ECU # enum for marking which ECU a command should listen to +obd.logger # the OBD module's root logger (for debug) ```
From 96ac20d00bd3b1ab02ba7794e05ffb323945e550 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 18:47:02 -0400 Subject: [PATCH 103/128] more links and edits --- docs/Connections.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Connections.md b/docs/Connections.md index 749e05ea..04c249a6 100644 --- a/docs/Connections.md +++ b/docs/Connections.md @@ -20,13 +20,13 @@ connection = obd.OBD(ports[0]) # connect to the first port in the list
-### OBD(portstr=None, baudrate=38400, protocol=None, fast=True): +### OBD(portstr=None, baudrate=None, protocol=None, fast=True): `portstr`: The UNIX device file or Windows COM Port for your adapter. The default value (`None`) will auto select a port. -`baudrate`: The baudrate at which to set the serial connection. This can vary from adapter to adapter. Typical values are: 9600, 38400, 19200, 57600, 115200 +`baudrate`: The baudrate at which to set the serial connection. This can vary from adapter to adapter. Typical values are: 9600, 38400, 19200, 57600, 115200. The default value (`None`) will auto select a baudrate. -`protocol`: Forces python-OBD to use the given protocol when communicating with the adapter. See `protocol_id()` for possible values. The default value (`None`) will auto select a protocol. +`protocol`: Forces python-OBD to use the given protocol when communicating with the adapter. See [protocol_id()](Connections.md/#protocol_id) for possible values. The default value (`None`) will auto select a protocol. `fast`: Allows commands to be optimized before being sent to the car. Python-OBD currently makes two such optimizations: @@ -41,7 +41,7 @@ Disabling fast mode will guarantee that python-OBD outputs the unaltered command ### query(command, force=False) -Sends an `OBDCommand` to the car, and returns a `OBDResponse` object. This function will block until a response is received from the car. This function will also check whether the given command is supported by your car. If a command is not marked as supported, it will not be sent to the car, and an empty `Response` will be returned. To force an unsupported command to be sent, there is an optional `force` parameter for your convenience. +Sends an `OBDCommand` to the car, and returns an `OBDResponse` object. This function will block until a response is received from the car. This function will also check whether the given command is supported by your car. If a command is not marked as supported, it will not be sent, and an empty `OBDResponse` will be returned. To force an unsupported command to be sent, there is an optional `force` parameter for your convenience. *For non-blocking querying, see [Async Querying](Async Connections.md)* From a709860b8fe0309ee6586a2b01c825bbb6f63ca1 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 19:20:05 -0400 Subject: [PATCH 104/128] added example for adding support for OBDCommands --- docs/Connections.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/Connections.md b/docs/Connections.md index 04c249a6..c5d64402 100644 --- a/docs/Connections.md +++ b/docs/Connections.md @@ -142,8 +142,17 @@ Closes the connection. ### supported_commands -Property containing a list of commands that are supported by the car. +Property containing a `set` of commands that are supported by the car. +If you wish to manually mark a command as supported (prevents having to use `query(force=True)`), add the command to this set. This is not necessary when using python-OBD's builtin commands, but is useful if you create [custom commands](Custom Commands.md). + +```python +import obd +connection = obd.OBD() + +# manually mark the given command as supported +connection.supported_commands.add() +``` ---
From 2a5e9bd20d24d7a16072ab0f2ded11131ec00754 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 19:25:22 -0400 Subject: [PATCH 105/128] replaced retry with higher timeout --- obd/elm327.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/obd/elm327.py b/obd/elm327.py index 343420d3..ad058029 100644 --- a/obd/elm327.py +++ b/obd/elm327.py @@ -117,7 +117,7 @@ def __init__(self, portname, baudrate, protocol): parity = serial.PARITY_NONE, \ stopbits = 1, \ bytesize = 8, - timeout = 3) # seconds + timeout = 10) # seconds logger.info("Serial port successfully opened on " + self.port_name()) except serial.SerialException as e: @@ -429,7 +429,6 @@ def __read(self): logger.info("cannot perform __read() when unconnected") return "" - attempts = 2 buffer = bytearray() while True: @@ -438,14 +437,8 @@ def __read(self): # if nothing was recieved if not data: - - if attempts <= 0: - logger.warning("Failed to read port, giving up") - break - - logger.info("Failed to read port, trying again...") - attempts -= 1 - continue + logger.warning("Failed to read port") + break buffer.extend(data) From 7f2488c733f5832d5a210a7560a8a11a3cf5bd02 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 19:59:58 -0400 Subject: [PATCH 106/128] altered basic usage, placed red warnings for backwards compat and below 1.0.0 --- README.md | 4 ++-- docs/Commands.md | 2 ++ docs/Responses.md | 4 +++- docs/index.md | 6 ++++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 11862c8c..bf54aa1c 100644 --- a/README.md +++ b/README.md @@ -20,12 +20,12 @@ import obd connection = obd.OBD() # auto-connects to USB or RF port -cmd = obd.commands.RPM # select an OBD command (sensor) +cmd = obd.commands.SPEED # select an OBD command (sensor) response = connection.query(cmd) # send the command, and parse the response print(response.value) # returns unit-bearing values thanks to Pint -print(response.value.magnitude) # or simple floats +print(response.value.to("mph")) # user-friendly unit conversions ``` Documentation diff --git a/docs/Commands.md b/docs/Commands.md index fbd54eae..599fdd8b 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -208,6 +208,8 @@ Mode 03 contains a single command `GET_DTC` which requests all diagnostic troubl # Mode 06 +*WARNING: mode 06 is experimental. While it passes software tests, it has not been tested on a real vehicle. Any debug output for this mode would be greatly appreciated.* + Mode 06 commands are used to monitor various test results from the vehicle. All commands in this mode return the same datatype, as described in the [Monitor Response](Responses.md#monitors-mode-06-responses) section. Currently, mode 06 commands are only implemented for CAN protocols (ISO 15765-4). |PID | Name | Description | Response Value | diff --git a/docs/Responses.md b/docs/Responses.md index 6e4f37ce..802e924a 100644 --- a/docs/Responses.md +++ b/docs/Responses.md @@ -27,10 +27,12 @@ if not r.is_null(): # Pint Values -The `value` property typically contains a [Pint](http://pint.readthedocs.io/en/latest/) `Quantity` object, but can also hold complex structures (depending on the request). Pint quantities combine a value and unit into a single class, and are used to represent physical values (such as "4 seconds", and "88 mph"). This allows for consistency when doing math and unit conversions. Pint maintains a registry of units, which is exposed in python-OBD as `obd.Unit`. +The `value` property typically contains a [Pint](http://pint.readthedocs.io/en/latest/) `Quantity` object, but can also hold complex structures (depending on the request). Pint quantities combine a value and unit into a single class, and are used to represent physical values such as "4 seconds", and "88 mph". This allows for consistency when doing math and unit conversions. Pint maintains a registry of units, which is exposed in python-OBD as `obd.Unit`. Below are common operations that can be done with Pint units and quantities. For more information, check out the [Pint Documentation](http://pint.readthedocs.io/en/latest/). +*NOTE: for backwards compatibility with previous versions of python-OBD, use `response.value.magnitude` in place of `response.value`* + ```python import obd diff --git a/docs/index.md b/docs/index.md index 3ad138b3..7688edea 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,6 +2,8 @@ Python-OBD is a library for handling data from a car's [**O**n-**B**oard **D**iagnostics port](https://en.wikipedia.org/wiki/On-board_diagnostics) (OBD-II). It can stream real time sensor data, perform diagnostics (such as reading check-engine codes), and is fit for the Raspberry Pi. This library is designed to work with standard [ELM327 OBD-II adapters](http://www.amazon.com/s/ref=nb_sb_noss?field-keywords=elm327). +*NOTE: Python-OBD is below 1.0.0, meaning the API may change between minor versions. Consult the [GitHub release page](https://github.com/brendan-w/python-OBD/releases) for changelogs before updating.* +
# Installation @@ -27,12 +29,12 @@ import obd connection = obd.OBD() # auto-connects to USB or RF port -cmd = obd.commands.RPM # select an OBD command (sensor) +cmd = obd.commands.SPEED # select an OBD command (sensor) response = connection.query(cmd) # send the command, and parse the response print(response.value) # returns unit-bearing values thanks to Pint -print(response.value.magnitude) # or simple floats +print(response.value.to("mph")) # user-friendly unit conversions ``` OBD connections operate in a request-reply fashion. To retrieve data from the car, you must send commands that query for the data you want (e.g. RPM, Vehicle speed, etc). In python-OBD, this is done with the `query()` function. The commands themselves are represented as objects, and can be looked up by name or value in `obd.commands`. The `query()` function will return a response object with parsed data in its `value` property. From 9cd77c90d7a5c1045ef4f4c832dee678fdc777ed Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 20:23:40 -0400 Subject: [PATCH 107/128] bytearray now --- obd/protocols/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/obd/protocols/README.md b/obd/protocols/README.md index 7f6a250f..e5b9c193 100644 --- a/obd/protocols/README.md +++ b/obd/protocols/README.md @@ -1,9 +1,9 @@ Notes ----- -Each protocol object is callable, and accepts a list of raw input strings, and returns a list of parsed `Message` objects. The `data` field will contain a list of integers, corresponding to all relevant data returned by the command. +Each protocol object is callable, and accepts a list of raw input strings, and returns a list of parsed `Message` objects. The `data` field will contain a bytearray, corresponding to all relevant data returned by the command. -*Note: `Message.data` does not refer to the full data field of a message. Things like PCI/Mode/PID bytes are removed. If you want to see these fields, use `Frame.data` for the full (per-spec) data field.* +*Note: `Message.data` does not refer to the full data field of a message. Things like PCI/Mode/PID bytes are often removed. If you want to see these fields, use `Frame.data` for the full (per-spec) data field.* For example, these are the resultant `Message.data` fields for some single frame messages: @@ -15,7 +15,7 @@ A CAN Message: A J1850 Message: 48 6B 10 41 00 BE 7F B8 13 FF [ data ] -``` +``` The parsing itself (invoking `__call__`) is stateless. The only stateful part of a `Protocol` is the `ECU_Map`. These objects correlate OBD transmitter IDs (`tx_id`'s) with the various ECUs in the car. This way, `Message` objects can be marked with ECU constants such as: From 5574775362365b365d5224c84b40178e32f8d64d Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 21:49:34 -0400 Subject: [PATCH 108/128] deactivated mode 9 commands until they're implemented --- obd/commands.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/obd/commands.py b/obd/commands.py index 86dcf94e..9cb9ecfb 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -286,9 +286,9 @@ __mode9__ = [ # name description cmd bytes decoder ECU fast - OBDCommand("PIDS_9A" , "Supported PIDs [01-20]" , b"0900", 4, pid, ECU.ENGINE, True), - OBDCommand("VIN_MESSAGE_COUNT" , "VIN Message Count" , b"0901", 1, uas(0x01), ECU.ENGINE, True), - OBDCommand("VIN" , "Get Vehicle Identification Number" , b"0902", 20, raw_string, ECU.ENGINE, True), + # OBDCommand("PIDS_9A" , "Supported PIDs [01-20]" , b"0900", 4, pid, ECU.ENGINE, True), + # OBDCommand("VIN_MESSAGE_COUNT" , "VIN Message Count" , b"0901", 1, uas(0x01), ECU.ENGINE, True), + # OBDCommand("VIN" , "Get Vehicle Identification Number" , b"0902", 20, raw_string, ECU.ENGINE, True), ] __misc__ = [ From 0279782b6e9a060408709325a570db4caa68d3f7 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 22:13:53 -0400 Subject: [PATCH 109/128] enable STATUS_DRIVE_CYCLE --- docs/Commands.md | 2 +- obd/commands.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Commands.md b/docs/Commands.md index 599fdd8b..1b6b1ae0 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -139,7 +139,7 @@ obd.commands.has_pid(1, 12) # True | 3E | CATALYST_TEMP_B1S2 | Catalyst Temperature: Bank 1 - Sensor 2 | Unit.celsius | | 3F | CATALYST_TEMP_B2S2 | Catalyst Temperature: Bank 2 - Sensor 2 | Unit.celsius | | 40 | PIDS_C | Supported PIDs [41-60] | bitarray | -| 41 | *unsupported* | *unsupported* | | +| 41 | STATUS_DRIVE_CYCLE | Monitor status this drive cycle | [special](Responses.md#status) | | 42 | *unsupported* | *unsupported* | | | 43 | *unsupported* | *unsupported* | | | 44 | *unsupported* | *unsupported* | | diff --git a/obd/commands.py b/obd/commands.py index 9cb9ecfb..d5df3f2a 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -119,7 +119,7 @@ # name description cmd bytes decoder ECU fast OBDCommand("PIDS_C" , "Supported PIDs [41-60]" , b"0140", 4, pid, ECU.ENGINE, True), - OBDCommand("STATUS_DRIVE_CYCLE" , "Monitor status this drive cycle" , b"0141", 4, drop, ECU.ENGINE, True), + OBDCommand("STATUS_DRIVE_CYCLE" , "Monitor status this drive cycle" , b"0141", 4, status, ECU.ENGINE, True), OBDCommand("CONTROL_MODULE_VOLTAGE" , "Control module voltage" , b"0142", 2, drop, ECU.ENGINE, True), OBDCommand("ABSOLUTE_LOAD" , "Absolute load value" , b"0143", 2, drop, ECU.ENGINE, True), OBDCommand("COMMAND_EQUIV_RATIO" , "Command equivalence ratio" , b"0144", 2, drop, ECU.ENGINE, True), From 8e7a7c168e914257cb45a58c6a07bf6240b0dda1 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 22:16:53 -0400 Subject: [PATCH 110/128] enable CONTROL_MODULE_VOLTAGE --- docs/Commands.md | 2 +- obd/commands.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Commands.md b/docs/Commands.md index 1b6b1ae0..e9634c7b 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -140,7 +140,7 @@ obd.commands.has_pid(1, 12) # True | 3F | CATALYST_TEMP_B2S2 | Catalyst Temperature: Bank 2 - Sensor 2 | Unit.celsius | | 40 | PIDS_C | Supported PIDs [41-60] | bitarray | | 41 | STATUS_DRIVE_CYCLE | Monitor status this drive cycle | [special](Responses.md#status) | -| 42 | *unsupported* | *unsupported* | | +| 42 | CONTROL_MODULE_VOLTAGE | Control module voltage | Unit.volt | | 43 | *unsupported* | *unsupported* | | | 44 | *unsupported* | *unsupported* | | | 45 | RELATIVE_THROTTLE_POS | Relative throttle position | Unit.percent | diff --git a/obd/commands.py b/obd/commands.py index d5df3f2a..1f7b3c69 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -120,7 +120,7 @@ # name description cmd bytes decoder ECU fast OBDCommand("PIDS_C" , "Supported PIDs [41-60]" , b"0140", 4, pid, ECU.ENGINE, True), OBDCommand("STATUS_DRIVE_CYCLE" , "Monitor status this drive cycle" , b"0141", 4, status, ECU.ENGINE, True), - OBDCommand("CONTROL_MODULE_VOLTAGE" , "Control module voltage" , b"0142", 2, drop, ECU.ENGINE, True), + OBDCommand("CONTROL_MODULE_VOLTAGE" , "Control module voltage" , b"0142", 2, uas(0x0B), ECU.ENGINE, True), OBDCommand("ABSOLUTE_LOAD" , "Absolute load value" , b"0143", 2, drop, ECU.ENGINE, True), OBDCommand("COMMAND_EQUIV_RATIO" , "Command equivalence ratio" , b"0144", 2, drop, ECU.ENGINE, True), OBDCommand("RELATIVE_THROTTLE_POS" , "Relative throttle position" , b"0145", 1, percent, ECU.ENGINE, True), From aa9f8c1126591b6ec881717aa2ff6f3ddeef71e5 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 22:22:01 -0400 Subject: [PATCH 111/128] enable COMMANDED_EQUIV_RATIO --- docs/Commands.md | 2 +- obd/commands.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Commands.md b/docs/Commands.md index e9634c7b..9e67788b 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -142,7 +142,7 @@ obd.commands.has_pid(1, 12) # True | 41 | STATUS_DRIVE_CYCLE | Monitor status this drive cycle | [special](Responses.md#status) | | 42 | CONTROL_MODULE_VOLTAGE | Control module voltage | Unit.volt | | 43 | *unsupported* | *unsupported* | | -| 44 | *unsupported* | *unsupported* | | +| 44 | COMMANDED_EQUIV_RATIO | Commanded equivalence ratio | Unit.ratio | | 45 | RELATIVE_THROTTLE_POS | Relative throttle position | Unit.percent | | 46 | AMBIANT_AIR_TEMP | Ambient air temperature | Unit.celsius | | 47 | THROTTLE_POS_B | Absolute throttle position B | Unit.percent | diff --git a/obd/commands.py b/obd/commands.py index 1f7b3c69..e45a06eb 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -122,7 +122,7 @@ OBDCommand("STATUS_DRIVE_CYCLE" , "Monitor status this drive cycle" , b"0141", 4, status, ECU.ENGINE, True), OBDCommand("CONTROL_MODULE_VOLTAGE" , "Control module voltage" , b"0142", 2, uas(0x0B), ECU.ENGINE, True), OBDCommand("ABSOLUTE_LOAD" , "Absolute load value" , b"0143", 2, drop, ECU.ENGINE, True), - OBDCommand("COMMAND_EQUIV_RATIO" , "Command equivalence ratio" , b"0144", 2, drop, ECU.ENGINE, True), + OBDCommand("COMMANDED_EQUIV_RATIO" , "Commanded equivalence ratio" , b"0144", 2, uas(0x1E), ECU.ENGINE, True), OBDCommand("RELATIVE_THROTTLE_POS" , "Relative throttle position" , b"0145", 1, percent, ECU.ENGINE, True), OBDCommand("AMBIANT_AIR_TEMP" , "Ambient air temperature" , b"0146", 1, temp, ECU.ENGINE, True), OBDCommand("THROTTLE_POS_B" , "Absolute throttle position B" , b"0147", 1, percent, ECU.ENGINE, True), From 40f16d5704008099cb7163b7f032658b2cd5eabb Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 6 Jul 2016 22:30:02 -0400 Subject: [PATCH 112/128] enable ABSOLUTE_LOAD --- docs/Commands.md | 2 +- obd/commands.py | 2 +- obd/decoders.py | 7 +++++++ tests/test_decoders.py | 4 ++++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/Commands.md b/docs/Commands.md index 9e67788b..c799bcc7 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -141,7 +141,7 @@ obd.commands.has_pid(1, 12) # True | 40 | PIDS_C | Supported PIDs [41-60] | bitarray | | 41 | STATUS_DRIVE_CYCLE | Monitor status this drive cycle | [special](Responses.md#status) | | 42 | CONTROL_MODULE_VOLTAGE | Control module voltage | Unit.volt | -| 43 | *unsupported* | *unsupported* | | +| 43 | ABSOLUTE_LOAD | Absolute load value | Unit.percent | | 44 | COMMANDED_EQUIV_RATIO | Commanded equivalence ratio | Unit.ratio | | 45 | RELATIVE_THROTTLE_POS | Relative throttle position | Unit.percent | | 46 | AMBIANT_AIR_TEMP | Ambient air temperature | Unit.celsius | diff --git a/obd/commands.py b/obd/commands.py index e45a06eb..b5735ce2 100644 --- a/obd/commands.py +++ b/obd/commands.py @@ -121,7 +121,7 @@ OBDCommand("PIDS_C" , "Supported PIDs [41-60]" , b"0140", 4, pid, ECU.ENGINE, True), OBDCommand("STATUS_DRIVE_CYCLE" , "Monitor status this drive cycle" , b"0141", 4, status, ECU.ENGINE, True), OBDCommand("CONTROL_MODULE_VOLTAGE" , "Control module voltage" , b"0142", 2, uas(0x0B), ECU.ENGINE, True), - OBDCommand("ABSOLUTE_LOAD" , "Absolute load value" , b"0143", 2, drop, ECU.ENGINE, True), + OBDCommand("ABSOLUTE_LOAD" , "Absolute load value" , b"0143", 2, absolute_load, ECU.ENGINE, True), OBDCommand("COMMANDED_EQUIV_RATIO" , "Commanded equivalence ratio" , b"0144", 2, uas(0x1E), ECU.ENGINE, True), OBDCommand("RELATIVE_THROTTLE_POS" , "Relative throttle position" , b"0145", 1, percent, ECU.ENGINE, True), OBDCommand("AMBIANT_AIR_TEMP" , "Ambient air temperature" , b"0146", 1, temp, ECU.ENGINE, True), diff --git a/obd/decoders.py b/obd/decoders.py index f12ff41a..ce7b31c8 100644 --- a/obd/decoders.py +++ b/obd/decoders.py @@ -222,6 +222,13 @@ def o2_sensors_alt(messages): tuple(bits[6:]), # bank 4 ) +# 0 to 25700 % +def absolute_load(messages): + d = messages[0].data + v = bytes_to_int(d) + v *= 100.0 / 255.0 + return v * Unit.percent + def elm_voltage(messages): # doesn't register as a normal OBD response, # so access the raw frame data diff --git a/tests/test_decoders.py b/tests/test_decoders.py index 8c5e6977..5ef0f238 100644 --- a/tests/test_decoders.py +++ b/tests/test_decoders.py @@ -145,6 +145,10 @@ def test_aux_input_status(): assert d.aux_input_status(m("00")) == False assert d.aux_input_status(m("80")) == True +def test_absolute_load(): + assert d.absolute_load(m("0000")) == 0 * Unit.percent + assert d.absolute_load(m("FFFF")) == 25700 * Unit.percent + def test_elm_voltage(): # these aren't parsed as standard hex messages, so manufacture our own assert d.elm_voltage([ Message([ Frame("12.875") ]) ]) == 12.875 * Unit.volt From 9a147fceb90af7a98d70ea9d737efec53429b0a9 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 7 Jul 2016 01:20:17 -0400 Subject: [PATCH 113/128] fixed copy/paste typo --- docs/Responses.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Responses.md b/docs/Responses.md index 802e924a..30b992e6 100644 --- a/docs/Responses.md +++ b/docs/Responses.md @@ -174,8 +174,8 @@ response.value = ( (), # bank 0 is invalid, this is merely for correct indexing (True, True), # bank 1 (True, False), # bank 2 - (False, False), # bank 2 - (False, False) # bank 2 + (False, False), # bank 3 + (False, False) # bank 4 ) # example usage: From cc8ce34c3c2065e26655082f4b35e501096a54aa Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 7 Jul 2016 14:49:39 -0400 Subject: [PATCH 114/128] added mkdocs patch for RTFD --- docs/assets/extra.js | 48 ++++++++++++++++++++++++++++++++++++++++++++ mkdocs.yml | 18 +++++++++-------- 2 files changed, 58 insertions(+), 8 deletions(-) create mode 100644 docs/assets/extra.js diff --git a/docs/assets/extra.js b/docs/assets/extra.js new file mode 100644 index 00000000..5c927480 --- /dev/null +++ b/docs/assets/extra.js @@ -0,0 +1,48 @@ + +$(document).ready(function () { + fixSearch(); +}); + +/* + * RTD messes up MkDocs' search feature by tinkering with the search box defined in the theme, see + * https://github.com/rtfd/readthedocs.org/issues/1088. This function sets up a DOM4 MutationObserver + * to react to changes to the search form (triggered by RTD on doc ready). It then reverts everything + * the RTD JS code modified. + */ + +function fixSearch() +{ + var target = document.getElementById('rtd-search-form'); + var config = {attributes: true, childList: true}; + + var observer = new MutationObserver(function(mutations) { + // if it isn't disconnected it'll loop infinitely because the observed element is modified + observer.disconnect(); + var form = $('#rtd-search-form'); + form.empty(); + form.attr('action', 'https://' + window.location.hostname + '/en/' + determineSelectedBranch() + '/search.html'); + $('').attr({ + type: "text", + name: "q", + placeholder: "Search docs" + }).appendTo(form); + }); + + // don't run this outside RTD hosting + if (window.location.origin.indexOf('readthedocs') > -1) + { + observer.observe(target, config); + } +} + +function determineSelectedBranch() +{ + var branch = 'dev', path = window.location.pathname; + if (window.location.origin.indexOf('readthedocs') > -1) + { + // path is like /en///build/ -> extract 'lang' + // split[0] is an '' because the path starts with the separator + branch = path.split('/')[2]; + } + return branch; +} diff --git a/mkdocs.yml b/mkdocs.yml index d0800f49..186f92cf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,14 +1,16 @@ site_name: python-OBD repo_url: https://github.com/brendan-w/python-OBD repo_name: GitHub +extra_javascript: + - assets/extra.js pages: -- 'Getting Started': 'index.md' -- 'OBD Connections': 'Connections.md' -- 'Commands': 'Commands.md' -- 'Responses': 'Responses.md' -- 'Async Connections': 'Async Connections.md' -- 'Custom Commands': 'Custom Commands.md' -- 'Debug': 'Debug.md' -- 'Troubleshooting': 'Troubleshooting.md' + - 'Getting Started': 'index.md' + - 'OBD Connections': 'Connections.md' + - 'Commands': 'Commands.md' + - 'Responses': 'Responses.md' + - 'Async Connections': 'Async Connections.md' + - 'Custom Commands': 'Custom Commands.md' + - 'Debug': 'Debug.md' + - 'Troubleshooting': 'Troubleshooting.md' theme: readthedocs From b491a17263cab0e35f11f4dcb241bb9338679346 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Mon, 11 Jul 2016 13:59:57 -0400 Subject: [PATCH 115/128] return empty list instead of empty string --- obd/elm327.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obd/elm327.py b/obd/elm327.py index ad058029..db35f2e7 100644 --- a/obd/elm327.py +++ b/obd/elm327.py @@ -427,7 +427,7 @@ def __read(self): """ if not self.__port: logger.info("cannot perform __read() when unconnected") - return "" + return [] buffer = bytearray() From b120b81a978ff84da99a1e62b648f54f1a054ad2 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Mon, 11 Jul 2016 14:00:29 -0400 Subject: [PATCH 116/128] port_name now returns empty strings when no connection --- docs/Connections.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Connections.md b/docs/Connections.md index c5d64402..a6dbfa94 100644 --- a/docs/Connections.md +++ b/docs/Connections.md @@ -12,7 +12,7 @@ connection = obd.OBD("/dev/ttyUSB0") # create connection with USB 0 # OR -ports = obd.scan_serial() # return list of valid USB or RF ports +ports = obd.scan_serial() # return list of valid USB or RF ports print ports # ['/dev/ttyUSB0', '/dev/ttyUSB1'] connection = obd.OBD(ports[0]) # connect to the first port in the list ``` @@ -87,7 +87,7 @@ connection.status() == OBDStatus.CAR_CONNECTED ### port_name() -Returns the string name for the currently connected port (`"/dev/ttyUSB0"`). If no connection was made, this function returns `"Not connected to any port"`. +Returns the string name for the currently connected port (`"/dev/ttyUSB0"`). If no connection was made, this function returns an empty string. --- From 9b927154797ab965aec1ce58e18b6b4837a43048 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Mon, 11 Jul 2016 14:22:33 -0400 Subject: [PATCH 117/128] added CAN txid for the transmission --- obd/protocols/protocol_can.py | 1 + 1 file changed, 1 insertion(+) diff --git a/obd/protocols/protocol_can.py b/obd/protocols/protocol_can.py index 8d4b2191..e2381f79 100644 --- a/obd/protocols/protocol_can.py +++ b/obd/protocols/protocol_can.py @@ -41,6 +41,7 @@ class CANProtocol(Protocol): TX_ID_ENGINE = 0 + TX_ID_TRANSMISSION = 1 FRAME_TYPE_SF = 0x00 # single frame FRAME_TYPE_FF = 0x10 # first frame of multi-frame message From 7261a3520849b8c48438df176570180f55445a94 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Thu, 21 Jul 2016 15:46:23 -0400 Subject: [PATCH 118/128] moved command tables into their own page --- docs/Command Lookup.md | 59 +++++++++++++++++++++++ docs/{Commands.md => Command Tables.md} | 63 ------------------------- mkdocs.yml | 19 ++++---- 3 files changed, 69 insertions(+), 72 deletions(-) create mode 100644 docs/Command Lookup.md rename docs/{Commands.md => Command Tables.md} (95%) diff --git a/docs/Command Lookup.md b/docs/Command Lookup.md new file mode 100644 index 00000000..43e9ace7 --- /dev/null +++ b/docs/Command Lookup.md @@ -0,0 +1,59 @@ +`OBDCommand`s are objects used to query information from the vehicle. They contain all of the information neccessary to perform the query, and decode the cars response. Python-OBD has [built in tables](Command Tables.md) for the most common commands. They can be looked up by name, or by mode & PID. + +```python +import obd + +c = obd.commands.RPM + +# OR + +c = obd.commands['RPM'] + +# OR + +c = obd.commands[1][12] # mode 1, PID 12 (RPM) +``` + +The `commands` table also has a few helper methods for determining if a particular name or PID is present. + +--- + +### has_command(command) + +Checks the internal command tables for the existance of the given `OBDCommand` object. Commands are compared by mode and PID value. + +```python +import obd +obd.commands.has_command(obd.commands.RPM) # True +``` + +--- + +### has_name(name) + +Checks the internal command tables for a command with the given name. This is also the function of the `in` operator. + +```python +import obd + +obd.commands.has_name('RPM') # True + +# OR + +'RPM' in obd.commands # True +``` + +--- + +### has_pid(mode, pid) + +Checks the internal command tables for a command with the given mode and PID. + +```python +import obd +obd.commands.has_pid(1, 12) # True +``` + +--- + +
diff --git a/docs/Commands.md b/docs/Command Tables.md similarity index 95% rename from docs/Commands.md rename to docs/Command Tables.md index c799bcc7..f48ffa1a 100644 --- a/docs/Commands.md +++ b/docs/Command Tables.md @@ -1,66 +1,3 @@ - -# Lookup - -`OBDCommand`s are objects used to query information from the vehicle. They contain all of the information neccessary to perform the query, and decode the cars response. Python-OBD has built in tables for the most common commands. They can be looked up by name, or by mode & PID. - -```python -import obd - -c = obd.commands.RPM - -# OR - -c = obd.commands['RPM'] - -# OR - -c = obd.commands[1][12] # mode 1, PID 12 (RPM) -``` - -The `commands` table also has a few helper methods for determining if a particular name or PID is present. - ---- - -### has_command(command) - -Checks the internal command tables for the existance of the given `OBDCommand` object. Commands are compared by mode and PID value. - -```python -import obd -obd.commands.has_command(obd.commands.RPM) # True -``` - ---- - -### has_name(name) - -Checks the internal command tables for a command with the given name. This is also the function of the `in` operator. - -```python -import obd - -obd.commands.has_name('RPM') # True - -# OR - -'RPM' in obd.commands # True -``` - ---- - -### has_pid(mode, pid) - -Checks the internal command tables for a command with the given mode and PID. - -```python -import obd -obd.commands.has_pid(1, 12) # True -``` - ---- - -
- # OBD-II adapter (ELM327 commands) |PID | Name | Description | Response Value | diff --git a/mkdocs.yml b/mkdocs.yml index 186f92cf..bec2ac65 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,15 +2,16 @@ site_name: python-OBD repo_url: https://github.com/brendan-w/python-OBD repo_name: GitHub extra_javascript: - - assets/extra.js +- assets/extra.js pages: - - 'Getting Started': 'index.md' - - 'OBD Connections': 'Connections.md' - - 'Commands': 'Commands.md' - - 'Responses': 'Responses.md' - - 'Async Connections': 'Async Connections.md' - - 'Custom Commands': 'Custom Commands.md' - - 'Debug': 'Debug.md' - - 'Troubleshooting': 'Troubleshooting.md' +- 'Getting Started': 'index.md' +- 'OBD Connections': 'Connections.md' +- 'Command Lookup': 'Command Lookup.md' +- 'Command Tables' : 'Command Tables.md' +- 'Responses': 'Responses.md' +- 'Async Connections': 'Async Connections.md' +- 'Custom Commands': 'Custom Commands.md' +- 'Debug': 'Debug.md' +- 'Troubleshooting': 'Troubleshooting.md' theme: readthedocs From 237f89f034b858d11574ddfedd4a91039d460947 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Fri, 22 Jul 2016 14:19:23 -0400 Subject: [PATCH 119/128] enable ECU tagging for the transmission --- obd/protocols/protocol.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/obd/protocols/protocol.py b/obd/protocols/protocol.py index 2dce7383..2f925e31 100644 --- a/obd/protocols/protocol.py +++ b/obd/protocols/protocol.py @@ -127,6 +127,7 @@ class Protocol(object): # the TX_IDs of known ECUs TX_ID_ENGINE = None + TX_ID_TRANSMISSION = None def __init__(self, lines_0100): @@ -253,12 +254,16 @@ def populate_ecu_map(self, messages): # if any tx_ids are exact matches to the expected values, record them for m in messages: + if m.tx_id is None: + logger.debug("parse_frame failed to extract TX_ID") + continue + if m.tx_id == self.TX_ID_ENGINE: self.ecu_map[m.tx_id] = ECU.ENGINE found_engine = True + elif m.tx_id == self.TX_ID_TRANSMISSION: + self.ecu_map[m.tx_id] = ECU.TRANSMISSION # TODO: program more of these when we figure out their constants - # elif m.tx_id == self.TX_ID_TRANSMISSION: - # self.ecu_map[m.tx_id] = ECU.TRANSMISSION if not found_engine: # last resort solution, choose ECU with the most bits set From d76dae375134c2e5490ce70420e655c9e6de4251 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Fri, 22 Jul 2016 14:34:22 -0400 Subject: [PATCH 120/128] more debug info --- obd/elm327.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/obd/elm327.py b/obd/elm327.py index db35f2e7..357388bc 100644 --- a/obd/elm327.py +++ b/obd/elm327.py @@ -118,7 +118,7 @@ def __init__(self, portname, baudrate, protocol): stopbits = 1, \ bytesize = 8, timeout = 10) # seconds - logger.info("Serial port successfully opened on " + self.port_name()) + logger.info("Port opened") except serial.SerialException as e: self.__error(e) @@ -183,6 +183,8 @@ def set_protocol(self, protocol): def manual_protocol(self, protocol): + logger.debug("Setting fixed protocol: %s" % protocol) + r = self.__send(b"ATTP" + protocol.encode()) r0100 = self.__send(b"0100") @@ -204,6 +206,8 @@ def auto_protocol(self): and this function returns True """ + logger.debug("Choosing protocol automatically") + # -------------- try the ELM's auto protocol mode -------------- r = self.__send(b"ATSP0") @@ -249,10 +253,12 @@ def set_baudrate(self, baud): if baud is None: # when connecting to pseudo terminal, don't bother with auto baud if self.port_name().startswith("/dev/pts"): + logger.debug("Detected pseudo terminal, skipping baudrate setup") return True else: return self.auto_baudrate() else: + logger.debug("Setting fixed baudrate: %d" % baud) self.__port.baudrate = baud return True @@ -315,14 +321,10 @@ def __has_message(self, lines, text): return False - def __error(self, msg=None): + def __error(self, msg): """ handles fatal failures, print logger.info info and closes serial """ - self.close() - - logger.error("Connection Error:") - if msg is not None: - logger.error(str(msg)) + logger.error(str(msg)) def port_name(self): From a47ade52d1fa2e11ecc63f662313b641d864e731 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Fri, 22 Jul 2016 15:06:01 -0400 Subject: [PATCH 121/128] more debug tweaking --- obd/elm327.py | 29 +++++++++++++++-------------- obd/obd.py | 5 +++-- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/obd/elm327.py b/obd/elm327.py index 357388bc..7bc776bb 100644 --- a/obd/elm327.py +++ b/obd/elm327.py @@ -105,6 +105,13 @@ class ELM327: def __init__(self, portname, baudrate, protocol): """Initializes port by resetting device and gettings supported PIDs. """ + logger.info("Initializing ELM327: PORT=%s BAUD=%s PROTOCOL=%s" % + ( + portname, + "auto" if baudrate is None else baudrate, + "auto" if protocol is None else protocol, + )) + self.__status = OBDStatus.NOT_CONNECTED self.__port = None self.__protocol = UnknownProtocol([]) @@ -112,14 +119,11 @@ def __init__(self, portname, baudrate, protocol): # ------------- open port ------------- try: - logger.info("Opening serial port '%s'" % portname) self.__port = serial.Serial(portname, \ parity = serial.PARITY_NONE, \ stopbits = 1, \ bytesize = 8, timeout = 10) # seconds - logger.info("Port opened") - except serial.SerialException as e: self.__error(e) return @@ -164,7 +168,12 @@ def __init__(self, portname, baudrate, protocol): # try to communicate with the car, and load the correct protocol parser if self.set_protocol(protocol): self.__status = OBDStatus.CAR_CONNECTED - logger.info("Connection successful") + logger.info("Connected Successfully: PORT=%s BAUD=%s PROTOCOL=%s" % + ( + portname, + self.__protocol.ELM_ID, + self.__port.baudrate, + )) else: logger.error("Connected to the adapter, but failed to connect to the vehicle") @@ -182,9 +191,6 @@ def set_protocol(self, protocol): def manual_protocol(self, protocol): - - logger.debug("Setting fixed protocol: %s" % protocol) - r = self.__send(b"ATTP" + protocol.encode()) r0100 = self.__send(b"0100") @@ -206,8 +212,6 @@ def auto_protocol(self): and this function returns True """ - logger.debug("Choosing protocol automatically") - # -------------- try the ELM's auto protocol mode -------------- r = self.__send(b"ATSP0") @@ -234,7 +238,7 @@ def auto_protocol(self): # an unknown protocol # this is likely because not all adapter/car combinations work # in "auto" mode. Some respond to ATDPN responded with "0" - logger.info("ELM responded with unknown protocol. Trying them one-by-one") + logger.debug("ELM responded with unknown protocol. Trying them one-by-one") for p in self._TRY_PROTOCOL_ORDER: r = self.__send(b"ATTP" + p.encode()) @@ -258,7 +262,6 @@ def set_baudrate(self, baud): else: return self.auto_baudrate() else: - logger.debug("Setting fixed baudrate: %d" % baud) self.__port.baudrate = baud return True @@ -269,8 +272,6 @@ def auto_baudrate(self): Returns boolean for success. """ - logger.debug("Choosing baudrate automatically") - # before we change the timout, save the "normal" value timeout = self.__port.timeout self.__port.timeout = 0.1 # we're only talking with the ELM, so things should go quickly @@ -399,7 +400,7 @@ def __send(self, cmd, delay=None): self.__write(cmd) if delay is not None: - logger.info("wait: %d seconds" % delay) + logger.debug("wait: %d seconds" % delay) time.sleep(delay) return self.__read() diff --git a/obd/obd.py b/obd/obd.py index 654dcf5b..e3ad3732 100644 --- a/obd/obd.py +++ b/obd/obd.py @@ -99,7 +99,7 @@ def __load_commands(self): logger.warning("Cannot load commands: No connection to car") return - logger.info("querying for supported PIDs (commands)...") + logger.info("querying for supported commands") pid_getters = commands.pid_getters() for get in pid_getters: # PID listing commands should sequentialy become supported @@ -109,9 +109,10 @@ def __load_commands(self): # when querying, only use the blocking OBD.query() # prevents problems when query is redefined in a subclass (like Async) - response = OBD.query(self, get, force=True) # ask nicely + response = OBD.query(self, get) if response.is_null(): + logger.info("No valid data for PID listing command: %s" % get) continue # loop through PIDs bitarray From 5f1c9aa908f7855680928d302ee6311910f2bffb Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Fri, 22 Jul 2016 15:10:03 -0400 Subject: [PATCH 122/128] prettier ECU map debug output --- obd/protocols/protocol.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/obd/protocols/protocol.py b/obd/protocols/protocol.py index 2f925e31..c8e7ab71 100644 --- a/obd/protocols/protocol.py +++ b/obd/protocols/protocol.py @@ -153,7 +153,8 @@ def __init__(self, lines_0100): # log out the ecu map for tx_id, ecu in self.ecu_map.items(): names = [k for k in ECU.__dict__ if ECU.__dict__[k] == ecu ] - logger.debug("Chose ECU %d as %s" % (tx_id, names)) + names = ", ".join(names) + logger.debug("map ECU %d --> %s" % (tx_id, names)) def __call__(self, lines): From 0c1f164a08eaf60d9c4642422f80b7f2c92f4c02 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Fri, 22 Jul 2016 15:13:08 -0400 Subject: [PATCH 123/128] log baud/protocol in the correct places --- obd/elm327.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obd/elm327.py b/obd/elm327.py index 7bc776bb..19e45b7f 100644 --- a/obd/elm327.py +++ b/obd/elm327.py @@ -171,8 +171,8 @@ def __init__(self, portname, baudrate, protocol): logger.info("Connected Successfully: PORT=%s BAUD=%s PROTOCOL=%s" % ( portname, - self.__protocol.ELM_ID, self.__port.baudrate, + self.__protocol.ELM_ID, )) else: logger.error("Connected to the adapter, but failed to connect to the vehicle") From 70ecdb59b8e7a1f08f24f85f752fd912c3e0a34a Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Mon, 25 Jul 2016 20:57:42 -0400 Subject: [PATCH 124/128] return early if set_baudrate fails --- obd/elm327.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/obd/elm327.py b/obd/elm327.py index 19e45b7f..f4175eea 100644 --- a/obd/elm327.py +++ b/obd/elm327.py @@ -134,7 +134,8 @@ def __init__(self, portname, baudrate, protocol): # ------------------------ find the ELM's baud ------------------------ if not self.set_baudrate(baudrate): - self.__error("Failed to set baudrate"); + self.__error("Failed to set baudrate") + return # ---------------------------- ATZ (reset) ---------------------------- try: From a22c1616b118af94009d5aff3188742e3260a593 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Mon, 25 Jul 2016 21:20:51 -0400 Subject: [PATCH 125/128] added UAS to the control flow diagram --- obd/README.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/obd/README.md b/obd/README.md index 98aa3cc3..1a145d0e 100644 --- a/obd/README.md +++ b/obd/README.md @@ -1,18 +1,15 @@ - -Notes ------ - ``` + API ┌───────────────────────┐ -│ obd.py (API) │ +│ obd.py / async.py │ └───┰───────────────────┘ ┃ ▲ ┃ ┃ -┌───╂───────────────╂───┐ ┌─────────────────┐ -│ ┃ ┗━━━┿━━━━━━┥ │ -│ ┃ OBDCommand.py │ │ decoders.py │ -│ ┃ ┏━━━┿━━━━ ▶│ │ -└───╂───────────────╂───┘ └─────────────────┘ +┌───╂───────────────╂───┐ ┌─────────────────┐ ┌────────────────────┐ +│ ┃ ┗━━━┿━━━━━━┥ │◀ ━━━━━━━┥ │ +│ ┃ OBDCommand.py │ │ decoders.py │ (maybe) │ UnitsAndScaling.py │ +│ ┃ ┏━━━┿━━━━ ▶│ ┝━━━━━━━ ▶│ │ +└───╂───────────────╂───┘ └─────────────────┘ └────────────────────┘ ┃ ┃ ┃ ┃ ┌───╂───────────────╂───┐ ┌─────────────────┐ @@ -25,4 +22,5 @@ Notes ┌───────────────────┸───┐ │ pyserial │ └───────────────────────┘ + Serial Port ``` From d9d7ff82bf242b9b575a565c0d91c7c20bf61ddf Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Mon, 25 Jul 2016 21:30:22 -0400 Subject: [PATCH 126/128] added notes on files that aren't in the diagram --- obd/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/obd/README.md b/obd/README.md index 1a145d0e..7788290e 100644 --- a/obd/README.md +++ b/obd/README.md @@ -24,3 +24,9 @@ └───────────────────────┘ Serial Port ``` + +Not pictured: + +- `commands.py` : defines the various OBD commands, and which decoder they use +- `codes.py` : stores tables of standardized values needed by `decoders.py` (mostly check-engine codes) +- `OBDResponse.py` : defines structures/objects returned by the API in response to a query. From 348e5cfac57b416b85998a7e7beed9da86f60f58 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 27 Jul 2016 17:42:30 -0400 Subject: [PATCH 127/128] elevated debug level to warning --- obd/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obd/__init__.py b/obd/__init__.py index 1a5c9d1b..dc96b281 100644 --- a/obd/__init__.py +++ b/obd/__init__.py @@ -49,7 +49,7 @@ import logging logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) +logger.setLevel(logging.WARNING) console_handler = logging.StreamHandler() # sends output to stderr console_handler.setFormatter(logging.Formatter("[%(name)s] %(message)s")) From b4fcfaa98ca0625e193fd2a7ffbc3de203da1df2 Mon Sep 17 00:00:00 2001 From: Brendan Whitfield Date: Wed, 27 Jul 2016 17:50:55 -0400 Subject: [PATCH 128/128] silence test_cmd output when scanning PID support --- obd/obd.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/obd/obd.py b/obd/obd.py index e3ad3732..34899651 100644 --- a/obd/obd.py +++ b/obd/obd.py @@ -104,7 +104,7 @@ def __load_commands(self): for get in pid_getters: # PID listing commands should sequentialy become supported # Mode 1 PID 0 is assumed to always be supported - if not self.supports(get): + if not self.test_cmd(get, warn=False): continue # when querying, only use the blocking OBD.query() @@ -214,19 +214,21 @@ def supports(self, cmd): return cmd in self.supported_commands - def test_cmd(self, cmd): + def test_cmd(self, cmd, warn=True): """ Returns a boolean for whether a command will be sent without using force=True. """ # test if the command is supported if not self.supports(cmd): - logger.warning("'%s' is not supported" % str(cmd)) + if warn: + logger.warning("'%s' is not supported" % str(cmd)) return False # mode 06 is only implemented for the CAN protocols if cmd.mode == 6 and self.interface.protocol_id() not in ["6", "7", "8", "9"]: - logger.warning("Mode 06 commands are only supported over CAN protocols") + if warn: + logger.warning("Mode 06 commands are only supported over CAN protocols") return False return True