Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support ELM327 error messages #87

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
8 changes: 5 additions & 3 deletions obd/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,15 +287,17 @@

__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),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were you able to get VIN to work?

]

__misc__ = [
# name description cmd bytes decoder ECU fast
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),
OBDCommand("FAKE_COMMAND_1" , "Non-existing command returning error" , b"ATXYZ", 0, raw_string, ECU.UNKNOWN, False),

Copy link
Owner

@brendan-w brendan-w Jul 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used for anything here besides development? I don't think python-OBD needs to be shipped with a fake command.

]


Expand Down
54 changes: 42 additions & 12 deletions obd/elm327.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,24 @@ class ELM327:
# going to be less picky about the time required to detect it.
_TRY_BAUDS = [ 38400, 9600, 230400, 115200, 57600, 19200 ]

_ERROR_MESSAGES = [
[b"?", "?: ELM327 reports misundersting command received on the RS232 input."],
[b"ACT ALERT", "ACT ALERT: ELM327 warns no RS232 activity. Might switch to low-power standby mode."],
[b"BUFFER FULL", "BUFFER FULL: ELM327 reports that RS232 buffer is filling at a faster rate than transmission. Increase baud speed."],
[b"BUS BUSY", "BUS BUSY: ELM327 reports too much activity on BUS. Check wiring."],
[b"BUS ERROR", "BUS ERROR: ELM327 reports a BUS error. This might be normal when starting CAN listening."],
[b"CAN ERROR", "CAN ERROR: ELM327 reports a CAN system ERROR. CAN has difficulty initializing, sending or receiving."],
[b"<DATA ERROR", "<DATA ERROR: ELM327 reports an error in the line number following."],
[b"DATA ERROR", "DATA ERROR: ELM327 reports an error response from vehicule, data lost."],
[b"FB ERROR", "EFB ERROR: ELM327 reports a feedBack(FB) error affecting signal. Check cable."],
[b"LP ALERT", "LP ALERT: ELM327 is about to switch to the Low Power (standby) mode within 2 seconds."],
[b"LV RESET", "LV RESET: ELM327 performed a low-voltage reset."],
[b"NO DATA", "NO DATA: ELM327 returns no data, either because answer is empty or not understood or not supported."],
[b"<RX ERROR", "<RX ERROR: ELM327 reports an error in the received CAN data. Check connection parameters."],
[b"STOPPED", "STOPPED: ELM327 reports that OBD operation was interrupted by a received RS232 character, or by a low level on the RTS pin."],
[b"UNABLE TO CONNECT", "UNABLE TO CONNECT: ELM327 tried all available protocols, and could not detect a compatible one."],
[b"ERR", "ERR: ELM327 reports an error number following."]
]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome, thanks!

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be turned into a dictionary for clarity?

{
    b"?" : "?: ELM327 reports misundersting command received on the RS232 input.",
    ...
}



def __init__(self, portname, baudrate, protocol):
Expand All @@ -117,7 +135,6 @@ def __init__(self, portname, baudrate, protocol):
self.__port = None
self.__protocol = UnknownProtocol([])


# ------------- open port -------------
try:
self.__port = serial.Serial(portname, \
Expand Down Expand Up @@ -258,7 +275,7 @@ def auto_protocol(self):
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"):
if self.get_port_name().startswith("/dev/pts"):
logger.debug("Detected pseudo terminal, skipping baudrate setup")
return True
else:
Expand Down Expand Up @@ -330,29 +347,36 @@ def __error(self, msg):
logger.error(str(msg))


def port_name(self):
def get_port_name(self):
if self.__port is not None:
return self.__port.portstr
else:
return ""


def status(self):
return self.__status


def ecus(self):
return self.__protocol.ecu_map.values()
def get_port_baudrate(self):
if self.__port is not None:
return self.__port.baudrate
else:
return ""


def protocol_name(self):
def get_protocol_name(self):
return self.__protocol.ELM_NAME


def protocol_id(self):
def get_protocol_id(self):
return self.__protocol.ELM_ID


def get_ecus(self):
return self.__protocol.ecu_map.values()


def status(self):
return self.__status


def close(self):
"""
Resets the device, and sets all
Expand Down Expand Up @@ -442,14 +466,20 @@ def __read(self):

# if nothing was recieved
if not data:
logger.warning("Failed to read port")
logger.warning("Failed to read port: empty data.")
break

buffer.extend(data)

# end on chevron (ELM prompt character)
if self.ELM_PROMPT in buffer:
break

#Check errors received by ELM327 and write debug
for iError in self._ERROR_MESSAGES:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor nit-pick: in python, we typically prefer lower-case, underscore separated names. For this, simply error would suffice (especially if we used the dictionary approach I mentioned above, which iterates over keys).

if iError[0] in buffer:
logger.debug(iError[1])
break

# log, and remove the "bytearray( ... )" part
logger.debug("read: " + repr(buffer)[10:-1])
Expand Down
71 changes: 55 additions & 16 deletions obd/obd.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,40 +155,46 @@ def status(self):
return self.interface.status()


# not sure how useful this would be

# def ecus(self):
# """ returns a list of ECUs in the vehicle """
# if self.interface is None:
# return []
# else:
# return self.interface.ecus()
def get_ecus(self):
""" returns a list of ECUs in the vehicle """
if self.interface is None:
return []
else:
return self.interface.get_ecus()


def protocol_name(self):
def get_protocol_name(self):
""" returns the name of the protocol being used by the ELM327 """
if self.interface is None:
return ""
else:
return self.interface.protocol_name()
return self.interface.get_protocol_name()


def protocol_id(self):
def get_protocol_id(self):
""" returns the ID of the protocol being used by the ELM327 """
if self.interface is None:
return ""
else:
return self.interface.protocol_id()
return self.interface.get_protocol_id()


def port_name(self):
def get_port_name(self):
""" Returns the name of the currently connected port """
if self.interface is not None:
return self.interface.port_name()
return self.interface.get_port_name()
else:
return ""


def get_port_baudrate(self):
""" Returns the speed of the currently connected port """
if self.interface is not None:
return str(self.interface.get_port_baudrate())
else:
return ""


def is_connected(self):
"""
Returns a boolean for whether a connection with the car was made.
Expand All @@ -207,13 +213,46 @@ def print_commands(self):
for c in self.supported_commands:
print(str(c))


def print_discovered(self):
"""
Utility function meant to print all information discovered:
protocol, port name, port baudrate and all supported commands.
"""
if self.interface is not None:
print ("The following settings were used to connect to the ECU:")
print ("Protocole: " + self.get_protocol_name())
print ("Port name: " + self.get_port_name())
print ("Port rate: " + self.get_port_baudrate())
print ("The following OBD commands are supported:")

_mylist=[]
for c in self.supported_commands:
_mylist.append(str(c))
_mylist.sort()
for i in _mylist:
print (i)

else:
print ("Impossible to print discovered information: no connection to the ECU.")

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind rebasing-out these changes? (assuming that they aren't co-dependant) That way, the two changes can cleanly come in via two PR's. Same with the addition of the get_ prefixes; Discuss that separately.

def supports(self, cmd):
"""
Returns a boolean for whether the given command
is supported by the car
"""
return cmd in self.supported_commands


def get_supported_commands(self):
"""
Returns a list of commands
supported by the car
"""

if self.interface is not None:
return self.supported_commands
else:
return []


def test_cmd(self, cmd, warn=True):
Expand All @@ -228,7 +267,7 @@ def test_cmd(self, cmd, warn=True):
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"]:
if cmd.mode == 6 and self.interface.get_protocol_id() not in ["6", "7", "8", "9"]:
if warn:
logger.warning("Mode 06 commands are only supported over CAN protocols")
return False
Expand Down
18 changes: 9 additions & 9 deletions tests/test_OBD.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def __init__(self, portname, UNUSED_baudrate=None, UNUSED_protocol=None):
self._status = OBDStatus.CAR_CONNECTED
self._last_command = None

def port_name(self):
def get_port_name(self):
return self._portname

def status(self):
Expand All @@ -32,10 +32,10 @@ def status(self):
def ecus(self):
return [ ECU.ENGINE, ECU.UNKNOWN ]

def protocol_name(self):
def get_protocol_name(self):
return "ISO 15765-4 (CAN 11/500)"

def protocol_id(self):
def get_protocol_id(self):
return "6"

def close(self):
Expand Down Expand Up @@ -122,30 +122,30 @@ def test_port_name():
"""
o = obd.OBD("/dev/null")
o.interface = FakeELM("/dev/null")
assert o.port_name() == o.interface._portname
assert o.get_port_name() == o.interface.get_port_name()

o.interface = FakeELM("A different port name")
assert o.port_name() == o.interface._portname
assert o.get_port_name() == o.interface.get_port_name()


def test_protocol_name():
o = obd.OBD("/dev/null")

o.interface = None
assert o.protocol_name() == ""
assert o.get_protocol_name() == ""

o.interface = FakeELM("/dev/null")
assert o.protocol_name() == o.interface.protocol_name()
assert o.get_protocol_name() == o.interface.get_protocol_name()


def test_protocol_id():
o = obd.OBD("/dev/null")

o.interface = None
assert o.protocol_id() == ""
assert o.get_protocol_id() == ""

o.interface = FakeELM("/dev/null")
assert o.protocol_id() == o.interface.protocol_id()
assert o.get_protocol_id() == o.interface.get_protocol_id()



Expand Down