def _convert_value(self, raw_value): try: value = self._values[raw_value] except KeyError: untested() value = raw_value return value
def query_vehicle(interface): interface.open() interface.set_protocol(None) interface.connect_to_vehicle() responses = interface.send_request(OBDRequest(sid=0x01, pid=0x01)) # Find the response(s) for ECUs that support emissions monitors ecus = [] for r in responses: supported = [ m for m in r.non_continuous_monitors if m in r.supported_monitors() ] if len(supported) > 0: ecus.append(r) # If the engine's off (none support monitors), just pick the first if len(ecus) == 0: ecus.append(responses[0]) # Print the readiness status for ecu in ecus: if len(ecus) > 1: untested("multiple ECUs with supported monitors") print "ECU 0x%02X:" % response.header.tx_id check_readiness(ecu) interface.disconnect_from_vehicle() interface.close() return
def __init__(self, port, name=None, callback=None): """ port -- a SerialPort instance corresponding to the port to which the ELM device is attached name -- the descriptive name of the interface callback -- a function to be called with status updates during long operations (such as connecting to a vehicle); the one argument sent to the callback is a string containing a status message The callback may also be set (or changed) later, via set_status_callback(). """ assert type( self ) != ELM32X, "ELM32X should only be instantiated via obd.interface.elm.create()" assert isinstance(port, obd.serialport.SerialPort) if name is None: untested() name = "%s compatible" % self.__class__.__name__ Interface.__init__(self, port.name, name, callback) self.port = port self.interface_configured = False self.connected_to_vehicle = False return
def assemble_message(self, frames): """Return the reassembled bytes given the full set of received frames. Reassemble the data contained in the individual frames into the list of bytes comprising the complete message, excluding any frame headers or footers. frames -- the list of frames in the sequence """ if self.pid() in self._sequence_lengths: # include the SID and PID only once, at the beginning of the # reassembled message result = self.data_bytes[self.SID:self.PID+1] # SID+PID for frame in frames: if frame == None: untested("missing frames in non-CAN SID 09 message") # insert None for each missing byte in a missing frame result += [None] * (len(self.data_bytes) - (self.MC+1)) else: result += frame.data_bytes[self.MC+1:] # skip SID/PID/MessageCount else: assert len(frames) == 1 assert self.pid() in [MC_VIN, MC_CALID, MC_CVN, MC_IPT, MC_ECUNAME] # MessageCount requests are single-frame messages and don't have # any sequence number to skip result = self.data_bytes[:] return result
def assemble_message(self, frames): """Return the reassembled bytes given the full set of received frames. Reassemble the data contained in the individual frames into the list of bytes comprising the complete message, excluding any frame headers or footers. frames -- the list of frames in the sequence """ if self.pid() in self._sequence_lengths: # include the SID and PID only once, at the beginning of the # reassembled message result = self.data_bytes[self.SID:self.PID + 1] # SID+PID for frame in frames: if frame == None: untested("missing frames in non-CAN SID 09 message") # insert None for each missing byte in a missing frame result += [None] * (len(self.data_bytes) - (self.MC + 1)) else: result += frame.data_bytes[self.MC + 1:] # skip SID/PID/MessageCount else: assert len(frames) == 1 assert self.pid() in [MC_VIN, MC_CALID, MC_CVN, MC_IPT, MC_ECUNAME] # MessageCount requests are single-frame messages and don't have # any sequence number to skip result = self.data_bytes[:] return result
def __init__(self, message="Interface was processing previous command", response=None): untested("interface busy exception") OBDException.__init__(self, message) self.response = response return
def query_vehicle(interface): interface.open() interface.set_protocol(None) interface.connect_to_vehicle() responses = interface.send_request(OBDRequest(sid=0x01, pid=0x01)) # Find the response(s) for ECUs that support emissions monitors ecus = [] for r in responses: supported = [m for m in r.non_continuous_monitors if m in r.supported_monitors()] if len(supported) > 0: ecus.append(r) # If the engine's off (none support monitors), just pick the first if len(ecus) == 0: ecus.append(responses[0]) # Print the readiness status for ecu in ecus: if len(ecus) > 1: untested("multiple ECUs with supported monitors") print "ECU 0x%02X:" % response.header.tx_id check_readiness(ecu) interface.disconnect_from_vehicle() interface.close() return
def get_protocol(self): """Return the current protocol being used in communication with the vehicle. Raises an exception if not connected with a vehicle. """ if not self.connected_to_vehicle: raise CommandNotSupported("Not connected to vehicle") response = self.at_cmd("ATDPN") # suppress any "automatic" prefix if len(response) > 1 and response.startswith("A"): response = response[1:] # get the protocol object identified by the response try: protocol = self._supported_protocols[response] except KeyError as e: untested("unknown protocol returned by ELM") raise InterfaceError("Unknown protocol %r" % response) # bark if the protocol changed out from under us if self._protocol_response is None: self._protocol_response = response else: if response != self._protocol_response: untested("unexpected change in protocol") raise InterfaceError("Protocol changed unexpectedly") # return a copy to prevent muddling the internal list return copy.copy(protocol)
def __init__(self, id): untested("ELM-specific exception") InterfaceError.__init__( self, message="Internal ELM error; contact interface vendor", raw=id) return
def __str__(self): if not self.value: untested("null DTC") return "None" numeric = (self.value & 0x3FFF) alpha = (self.value >> 14) & 3 alpha = "PCBU"[alpha] return "%s%04X" % (alpha, numeric)
def sequence_length(self): """Return the number of frames in the sequence: None since the length is not known. The length is not specified in any legacy SID $03 frame. """ untested("non-CAN SID 03 sequence length") return None # variable number of messages (frames) in a response
def _return_raw_frames(self, raw_frames): """Return the list of raw frames untouched, but raise an exception if there were any data errors. """ for f in raw_frames: if None in f: untested("frames with data errors") raise DataError(raw=raw_frame) return raw_frames
def _return_bus_messages(self, raw_frames): """Reassemble a list of raw frames into complete BusMessages and raise an exception if there were any data errors; otherwise return the list of bus messages. """ bus_messages = self._process_obd_response(raw_frames) for r in bus_messages: if r.incomplete: untested("messages with bad frames") raise DataError(raw=bus_messages) return bus_messages
def _return_obd_responses(self, raw_frames): """Reassemble a list of raw frames into complete OBD responses, each represented as the appropriate Response subclass, and raise an exception if there were any data errors. Otherwise return the list of OBD responses. """ bus_messages = self._process_obd_response(raw_frames) obd_messages = [obd.message.create(m) for m in bus_messages] for r in obd_messages: if r.incomplete: untested("messages with bad frames") raise DataError(raw=obd_messages) return obd_messages
def set_protocol(self, protocol): """Select the protocol to use for communicating with the vehicle. This will disconnect any communication session with the vehicle already in progress. protocol -- the protocol to use, or None for automatic selection by the interface """ if self.connected_to_vehicle: self.disconnect_from_vehicle() for key, value in self._supported_protocols.items(): if value == protocol: self.at_cmd("ATTP %s" % key) break else: untested("unsupported protocol requested of ELM") raise ValueError("Unsupported protocol: %s" % str(protocol)) return
def _flush_frames(self): """Flush any pending messages and post them to the _complete_messages Queue. Messages may be pending because they are incomplete (e.g. a frame is missing) or because _received_obd_frame() was unable to determine that they were complete. This function should be called when it is determined that a response is complete. In the case of discrete request/response transactions, this is often trivial. In the case of bus monitoring, more complex logic must be employed to determine when frames should be flushed. """ try: # Iterate over all pending messages for frames, sequence_number, sequence_length in self._frames_received.values( ): # If the message is pending because it is incomplete if None in frames: untested("flushing incomplete messages") # Find the first received (non-None) frame, in case we missed the # first frame(s). The assertions check for bugs in received_frame(). for first_received in frames: if first_received != None: break else: assert False, "message with no frames received" else: # If the sequence length was known, the message should have been # flushed as soon as it was complete. sequence_length_known = (sequence_length is not None) assert not sequence_length_known, "message not flushed upon completion" first_received = frames[0] # Post the pending message header = first_received.header data = first_received.assemble_message(frames) bus_message = obd.message.BusMessage(header, data, frames) self._complete_messages.put(bus_message, False) finally: # Clear the pending messages self._frames_received = {} return
def _flush_frames(self): """Flush any pending messages and post them to the _complete_messages Queue. Messages may be pending because they are incomplete (e.g. a frame is missing) or because _received_obd_frame() was unable to determine that they were complete. This function should be called when it is determined that a response is complete. In the case of discrete request/response transactions, this is often trivial. In the case of bus monitoring, more complex logic must be employed to determine when frames should be flushed. """ try: # Iterate over all pending messages for frames, sequence_number, sequence_length in self._frames_received.values(): # If the message is pending because it is incomplete if None in frames: untested("flushing incomplete messages") # Find the first received (non-None) frame, in case we missed the # first frame(s). The assertions check for bugs in received_frame(). for first_received in frames: if first_received != None: break else: assert False, "message with no frames received" else: # If the sequence length was known, the message should have been # flushed as soon as it was complete. sequence_length_known = (sequence_length is not None) assert not sequence_length_known, "message not flushed upon completion" first_received = frames[0] # Post the pending message header = first_received.header data = first_received.assemble_message(frames) bus_message = obd.message.BusMessage(header, data, frames) self._complete_messages.put(bus_message, False) finally: # Clear the pending messages self._frames_received = {} return
def __init__(self, port, name=None, callback=None): """ port -- a SerialPort instance corresponding to the port to which the ELM device is attached name -- the descriptive name of the interface callback -- a function to be called with status updates during long operations (such as connecting to a vehicle); the one argument sent to the callback is a string containing a status message The callback may also be set (or changed) later, via set_status_callback(). """ assert type(self) != ELM32X, "ELM32X should only be instantiated via obd.interface.elm.create()" assert isinstance(port, obd.serialport.SerialPort) if name is None: untested() name = "%s compatible" % self.__class__.__name__ Interface.__init__(self, port.name, name, callback) self.port = port self.interface_configured = False self.connected_to_vehicle = False return
def create(identifier, argument_name=""): """Return the interface specified by the given identifier, raising an exception if the interface cannot be created. identifier -- the identifier specifying the desired interface; this might be the serial port to which the interface is attached, or some other identifier (such as a USB serial number) used to specify the desired interface. argument_name -- text returned in exception messages explaining how to specify the identifier, e.g. via a "--port" command-line option If identifier is None, try to detect the interface automatically. a) If enumeration is not supported, this will raise an OBDException. b) If more than one interface is detected, this will raise a ValueError exception listing the detected interfaces c) If no interfaces are detected, this will raise an InterfaceNotFound exception. """ if identifier is not None: for class_ in _interface_classes: try: interface = class_.create(identifier) break except: pass else: message = "Unable to connect to scanner at %s; " % identifier message += "is it connected and powered?\n" raise InterfaceNotFound(message) else: if argument_name: select = " via %s" % argument_name else: untested("no argument name") select = "" select = "Please select an interface to use" + select try: interfaces = obd.interface.enumerate() except OBDException as e: untested("enumeration not supported") message = "%s.\n%s.\n" % (str(e), select) raise OBDException(message) if len(interfaces) < 1: message = "No scanners found; is one connected and powered?\n" raise InterfaceNotFound(message) if len(interfaces) > 1: untested("multiple interfaces") message = "%s:\n" % select for interface in interfaces: message += " %s\n" % str(interface) raise ValueError(message) interface = interfaces[0] return interface
def data_length(self): """Return the number of data bytes contained in the complete, reassembled sequence, or None if this frame has no such information. The length is only known for the SID $09 responses with a constant length assigned by specification. """ untested("non-CAN SID 09 data length") try: frames = self._sequence_lengths[self.pid()] if frames: untested("known number of frames in non-CAN SID 09 message") length = frames * 4 + 2 # 4 data bytes per frame + SID/PID for message else: untested("variable number of frames in non-CAN SID 09 message") length = None # variable number of bytes except KeyError: untested("non-CAN SID 09 MessageCount query") length = 3 # SID+PID + MessageCount byte for MessageCount requests return length
def create(port, callback=None, baud=None): """Create an instance of the appropriate ELM32X subclass at the given port port -- the SerialPort subclass to which the interface is attached callback -- the callback function used to provide status updates (default None) baud -- the baud rate to use, or None (default) to auto-detect """ # Use the appropriate baud rate, auto-detecting if requested #print "Reached here" if baud: if port.get_baudrate() != baud: untested("specifying the baud rate for obd.interface.elm.create()") port.set_baudrate(baud) else: current_baud = ELM32X.detect_baudrate(port) if not current_baud: raise InterfaceError("Unable to connect to ELM; does it have power?") #print "Reached here" # Query the interface for its identity identifier = ELM32X._at_cmd(port, "ATI") if identifier.startswith("ATI\r"): identifier = identifier[4:] debug(identifier) chip_identifier, chip_version = identifier.split(" ") #print identifier # Check for extended command set extended = ELM32X._at_cmd(port, "STI") if extended.startswith("STI\r"): extended = extended[4:] if extended != "?": untested(extended) chip_identifier, chip_version = extended.rsplit(" ", 1) print extended # Create an instance of the appropriate ELM32X subclass try: elm_class = _classes[chip_identifier] except KeyError as e: untested("unknown ELM response to ATI") raise InterfaceError("Unknown response to ATI: %r" % identifier) interface = elm_class(port, chip_identifier, callback=callback) debug("%s detected on port %s at %d baud" % (chip_identifier, interface.port.name, interface.port.get_baudrate())) return interface
def create(port, callback=None, baud=None): """Create an instance of the appropriate ELM32X subclass at the given port port -- the SerialPort subclass to which the interface is attached callback -- the callback function used to provide status updates (default None) baud -- the baud rate to use, or None (default) to auto-detect """ # Use the appropriate baud rate, auto-detecting if requested if baud: if port.get_baudrate() != baud: untested("specifying the baud rate for obd.interface.elm.create()") port.set_baudrate(baud) else: current_baud = ELM32X.detect_baudrate(port) if not current_baud: raise InterfaceError( "Unable to connect to ELM; does it have power?") # Query the interface for its identity identifier = ELM32X._at_cmd(port, "ATI") if identifier.startswith("ATI\r"): identifier = identifier[4:] debug(identifier) chip_identifier, chip_version = identifier.split(" ") # Check for extended command set extended = ELM32X._at_cmd(port, "STI") if extended.startswith("STI\r"): extended = extended[4:] if extended != "?": untested(extended) chip_identifier, chip_version = extended.rsplit(" ", 1) # Create an instance of the appropriate ELM32X subclass try: elm_class = _classes[chip_identifier] except KeyError as e: untested("unknown ELM response to ATI") raise InterfaceError("Unknown response to ATI: %r" % identifier) interface = elm_class(port, chip_identifier, callback=callback) debug( "%s detected on port %s at %d baud" % (chip_identifier, interface.port.name, interface.port.get_baudrate())) return interface
def assemble_message(self, frames): """Return the reassembled bytes given the full set of received frames. Reassemble the data contained in the individual frames into the list of bytes comprising the complete message, excluding any frame headers or footers. frames -- the list of frames in the sequence """ untested("non-CAN SID 03 reassembly") # include the SID only once, at the beginning of the reassembled message result = [self.data_bytes[self.SID]] # SID for frame in frames: if frame == None: untested("handling missing frame") # insert None for each missing byte in a missing frame result += [None] * (len(self.data_bytes) - (self.SID+1)) else: untested("assembling non-CAN frame") result += frame.data_bytes[self.SID+1:] # DTCs in each message return result
def assemble_message(self, frames): """Return the reassembled bytes given the full set of received frames. Reassemble the data contained in the individual frames into the list of bytes comprising the complete message, excluding any frame headers or footers. frames -- the list of frames in the sequence """ untested("non-CAN SID 03 reassembly") # include the SID only once, at the beginning of the reassembled message result = [self.data_bytes[self.SID]] # SID for frame in frames: if frame == None: untested("handling missing frame") # insert None for each missing byte in a missing frame result += [None] * (len(self.data_bytes) - (self.SID + 1)) else: untested("assembling non-CAN frame") result += frame.data_bytes[self.SID + 1:] # DTCs in each message return result
def __init__(self, message_data, offset, pid): untested("Diesel IPT response") Service09Response.__init__(self, message_data, offset, pid) if len(self.items) != 16: raise J1699Failure("IPT NODI != 16", raw=self) return
def __init__(self, value): self.value = value if not self.value: untested("null DTC") return
def _read_response(self, previous_data=""): """Read ASCII OBD frames from the interface until the prompt is received, and return the list of frames. Raises an exception on any error (such as no data, buffer overflow, etc.) previous_data -- data previously read from the interface which should be considered part of the response """ response = previous_data + self._read_until_prompt() response = response.strip("\r") lines = response.split("\r") for line in lines: # Raise exceptions for any errors if line == "?": raise CommandNotSupported() if line == "NO DATA": raise obd.exception.DataError(raw=line) if line.endswith("BUS BUSY") or line.endswith("DATA ERROR"): untested("data error") raise obd.exception.DataError(raw=line) if line.endswith("BUS ERROR") or line.endswith( "FB ERROR") or line.endswith("LV RESET"): untested("bus error") raise obd.exception.BusError(raw=line) if line.endswith("CAN ERROR") or line.endswith("RX ERROR"): untested("protocol error") raise obd.exception.ProtocolError(raw=line) if line.endswith("BUFFER FULL"): untested("buffer overflow") raise obd.exception.BufferOverflowError() if line.find("<DATA ERROR") != -1: untested("frame data error") # Once we have a test case, we should probably simply replace # lines with bad bytes with "None" for each byte; then # process_obd_response or send_request will raise the error. raise obd.exception.DataError(raw=line) matched = re.search(r"ERR\d\d", line) if (matched): untested("internal ELM error" ) # or does this only occur on connection? error = matched.group(0) if error == "ERR94": # ERR94 is a fatal CAN error according to p.52-53 of the ELM327 datasheet raise obd.exception.BusError(raw=line) raise ELM32XError(error) return lines
def __init__(self, message_data, offset, pid): untested() ValueResponse.__init__(self, message_data, offset, pid) return
def _read_response(self, previous_data=""): """Read ASCII OBD frames from the interface until the prompt is received, and return the list of frames. Raises an exception on any error (such as no data, buffer overflow, etc.) previous_data -- data previously read from the interface which should be considered part of the response """ response = previous_data + self._read_until_prompt() response = response.strip("\r") lines = response.split("\r") for line in lines: # Raise exceptions for any errors if line == "?": raise CommandNotSupported() if line == "NO DATA": raise obd.exception.DataError(raw=line) if line.endswith("BUS BUSY") or line.endswith("DATA ERROR"): untested("data error") raise obd.exception.DataError(raw=line) if line.endswith("BUS ERROR") or line.endswith("FB ERROR") or line.endswith("LV RESET"): untested("bus error") raise obd.exception.BusError(raw=line) if line.endswith("CAN ERROR") or line.endswith("RX ERROR"): untested("protocol error") raise obd.exception.ProtocolError(raw=line) if line.endswith("BUFFER FULL"): untested("buffer overflow") raise BufferOverflowError() if line.find("<DATA ERROR") != -1: untested("frame data error") # Once we have a test case, we should probably simply replace # lines with bad bytes with "None" for each byte; then # process_obd_response or send_request will raise the error. raise obd.exception.DataError(raw=line) matched = re.search(r"ERR\d\d", line) if (matched): untested("internal ELM error") # or does this only occur on connection? error = matched.group(0) if error == "ERR94": # ERR94 is a fatal CAN error according to p.52-53 of the ELM327 datasheet raise obd.exception.BusError(raw=line) raise ELM32XError(error) return lines
def __init__(self, message): untested("bus error exception") VehicleException.__init__(self, "Probable wiring error: %s" % message) return
def __init__(self, id): untested("ELM-specific exception") InterfaceError.__init__(self, message="Internal ELM error; contact interface vendor", raw=id) return