Skip to content

Commit 0ce70e4

Browse files
author
Brendan Whitfield
committed
began wiring in protocol system
1 parent ec67f40 commit 0ce70e4

8 files changed

Lines changed: 112 additions & 89 deletions

File tree

obd/OBDCommand.py

Lines changed: 19 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -32,55 +32,34 @@ def get_mode_int(self):
3232
def get_pid_int(self):
3333
return unhex(self.pid)
3434

35-
def compute(self, _data):
36-
# _data will be the string returned from the car (ELM adapter).
37-
# It should look something like this:
38-
#
39-
# Mode __Data___
40-
# | | |
41-
# "\r\r48 6B 10 41 00 BE 1F B8 11 AA\r\r"
42-
# || || ||
43-
# ECU PID Checksum
35+
def compute(self, messages):
4436

45-
# create the response object with the raw data recieved
46-
r = Response(_data)
47-
48-
# split by lines, and remove empty lines
49-
lines = filter(bool, re.split("[\r\n]", _data))
50-
51-
# splits each line by spaces (each element should be a hex byte)
52-
lines = [line.split() for line in lines]
37+
_bytes = []
5338

54-
# filter by minimum response length (number of space delimited chunks (bytes))
55-
lines = filter(lambda line: len(line) >= 7, lines)
39+
if len(messages) == 1:
40+
_bytes = messages[0].data_bytes
41+
else:
42+
pass
5643

57-
if len(lines) > 1:
58-
# filter for ECU 10 (engine)
59-
lines = filter(lambda line: line[2] == '10', lines)
6044

61-
# by now, we should have only one line.
62-
# Any more, and its a multiline response (which this library can't handle yet)
63-
if len(lines) == 0:
64-
debug("no valid data returned")
65-
elif len(lines) > 1:
66-
debug("multiline response returned, can't handle that (yet)")
67-
else: # len(lines) == 1
45+
# create the response object with the raw data recieved
46+
r = Response(message)
6847

69-
# combine the bytes back into a hex string, excluding the header + mode + pid, and trailing checksum
70-
_data = "".join(lines[0][5:-1])
48+
# combine the bytes back into a hex string, excluding the header + mode + pid, and trailing checksum
49+
_bytes = "".join(lines[0][5:-1])
7150

72-
if ("NODATA" not in _data) and isHex(_data):
51+
if ("NODATA" not in _data) and isHex(_data):
7352

74-
# constrain number of bytes in response
75-
if (self.bytes > 0): # zero bytes means flexible response
76-
_data = constrainHex(_data, self.bytes)
53+
# constrain number of bytes in response
54+
if (self.bytes > 0): # zero bytes means flexible response
55+
_data = constrainHex(_data, self.bytes)
7756

78-
# decoded value into the response object
79-
r.set(self.decode(_data))
57+
# decoded value into the response object
58+
r.set(self.decode(_data))
8059

81-
else:
82-
# not a parseable response
83-
debug("return data could not be decoded")
60+
else:
61+
# not a parseable response
62+
debug("return data could not be decoded")
8463

8564
return r
8665

obd/obd.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
########################################################################
3030

3131
import time
32-
from port import OBDPort, State
32+
from port import OBDPort
3333
from commands import commands
3434
from utils import scanSerial, Response
3535
from debug import debug
@@ -60,7 +60,7 @@ def connect(self, portstr=None):
6060

6161
self.port = OBDPort(port)
6262

63-
if(self.port.state == State.Connected):
63+
if self.port.is_connected():
6464
# success! stop searching for serial
6565
break
6666
else:
@@ -81,7 +81,6 @@ def close(self):
8181
self.port = None
8282

8383

84-
# checks the port state for conncetion status
8584
def is_connected(self):
8685
return (self.port is not None) and self.port.is_connected()
8786

@@ -145,15 +144,17 @@ def supports(self, c):
145144
def send(self, c):
146145
""" send the given command, retrieve and parse response """
147146

148-
# check for a connection
149147
if not self.is_connected():
150148
debug("Query failed, no connection available", True)
151149
return Response() # return empty response
152150

153-
# send the query
154151
debug("Sending command: %s" % str(c))
155-
r = self.port.write_and_read(c.get_command()) # send command and retrieve response
156-
return c.compute(r) # compute a response object
152+
153+
c_str = c.get_command()
154+
m = self.port.send_and_parse(c_str) # send command and retrieve message
155+
r = c.compute(m) # compute a response object
156+
157+
return r
157158

158159

159160
def query(self, c, force=False):

obd/port.py

Lines changed: 71 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,22 @@
2929
########################################################################
3030

3131
import serial
32-
import string
3332
import time
34-
from utils import Response, unhex
33+
import protocols
34+
from utils import strip
3535
from debug import debug
3636

3737

38-
class State():
39-
""" Enum for connection states """
40-
Unconnected, Connected = range(2)
41-
4238

4339
class OBDPort:
4440
""" OBDPort abstracts all communication with OBD-II device."""
4541

4642
def __init__(self, portname):
4743
"""Initializes port by resetting device and gettings supported PIDs. """
4844

49-
self.ELMver = "Unknown"
50-
self.state = State.Unconnected
51-
self.port = None
45+
self.connected = False
46+
self.port = None
47+
self.protocol = None
5248

5349
# ------------- open port -------------
5450

@@ -71,40 +67,58 @@ def __init__(self, portname):
7167

7268
debug("Serial port successfully opened on " + self.get_port_name())
7369

74-
# ------------- atz (reset) -------------
70+
71+
# ---------------------------- ATZ (reset) ----------------------------
7572
try:
76-
r = self.write_and_read("atz", 1) # wait 1 second for ELM to initialize
77-
r = self.__strip(r)
78-
if not r:
79-
self.__error("atz (reset) did not return with an ELM version")
80-
return
81-
self.ELMver = r
82-
73+
r = self.send("ATZ", delay=1) # wait 1 second for ELM to initialize
74+
# return data can be junk, so don't bother checking
8375
except serial.SerialException as e:
8476
self.__error(e)
8577
return
8678

87-
# ------------- ate0 (echo OFF) -------------
88-
r = self.write_and_read("ate0")
89-
r = self.__strip(r)
79+
80+
# -------------------------- ATE0 (echo OFF) --------------------------
81+
r = self.send("ATE0")
82+
r = strip(r)
9083
if (len(r) < 2) or (r[-2:] != "OK"):
91-
self.__error("ate0 did not return 'OK'")
84+
self.__error("ATE0 did not return 'OK'")
9285
return
9386

94-
# ------------- ath1 (headers ON) -------------
95-
r = self.write_and_read("ath1")
96-
r = self.__strip(r)
87+
88+
# ------------------------- ATH1 (headers ON) -------------------------
89+
r = self.send("ATH1")
90+
r = strip(r)
9791
if r != 'OK':
98-
self.__error("ath1 did not return 'OK', or echoing is still ON")
92+
self.__error("ATH1 did not return 'OK', or echoing is still ON")
9993
return
10094

101-
# ------------- done -------------
102-
debug("Connection successful")
103-
self.state = State.Connected
95+
96+
# ----------------------- ATSP0 (protocol AUTO) -----------------------
97+
r = self.send("ATSP0")
98+
r = strip(r)
99+
if r != 'OK':
100+
self.__error("ATSP0 did not return 'OK'")
101+
return
104102

105103

106-
def __strip(self, s):
107-
return "".join(s.split())
104+
# -------------- 0100 (first command, SEARCH protocols) --------------
105+
r = self.send("0100", delay=1) # give it a second to search
106+
107+
108+
# ----------------------- ATDP (list protocol) -----------------------
109+
r = self.send("ATDP")
110+
r = strip(r)
111+
protocol_class = protocols.get(r) # lookup the protocol by name
112+
if protocol_class is None:
113+
self.__error("ELM responded with unknown protocol")
114+
return
115+
116+
self.protocol = protocol_class()
117+
118+
119+
# ------------------------------- done -------------------------------
120+
debug("Connection successful")
121+
self.connected = True
108122

109123

110124
def __error(self, msg=None):
@@ -118,36 +132,53 @@ def __error(self, msg=None):
118132
if self.port is not None:
119133
self.port.close()
120134

121-
self.state = State.Unconnected
135+
self.connected = False
122136

123137

124138
def get_port_name(self):
125139
return self.port.portstr if (self.port is not None) else "No Port"
126140

127141

128142
def is_connected(self):
129-
return self.state == State.Connected
143+
return self.connected
130144

131145

132146
def close(self):
133147
""" Resets device and closes all associated filehandles"""
134148

135-
if (self.port != None) and (self.state == State.Connected):
136-
self.write("atz")
149+
if (self.port != None) and self.connected:
150+
self.__write("ATZ")
137151
self.port.close()
138152

139-
self.port = None
140-
self.ELMver = "Unknown"
153+
self.connected = False
154+
self.port = None
155+
self.protocol = None
156+
141157

158+
def send_and_parse(self, cmd, delay=None):
159+
160+
r = self.send(cmd, delay)
142161

143-
def write_and_read(self, cmd, delay=None):
162+
messages = self.protocol(r) # parses string into list of messages
163+
164+
# if more than one ECUs have responded, pick the primary
165+
# TODO: add support for more ECU types
166+
if len(messages) > 1:
167+
messages = filter(lambda m: m.tx_id == self.protocol.PRIMARY_ECU, messages)
168+
169+
return messages[0]
170+
171+
172+
def send(self, cmd, delay=None):
144173

145174
self.__write(cmd)
146175

147176
if delay is not None:
148177
time.sleep(delay)
149178

150-
return self.__read()
179+
r = self.__read()
180+
181+
return r # return raw string only
151182

152183

153184
# sends the hex string to the port
@@ -162,7 +193,8 @@ def __write(self, cmd):
162193
debug("cannot perform write() when unconnected", True)
163194

164195

165-
# accumulates and returns the ports response
196+
# accumulates until the prompt character is seen
197+
# returns raw string
166198
def __read(self):
167199

168200
attempts = 2

obd/protocols/message.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11

22

33
class Message(object):
4-
def __init__(self, frames, tx_id):
4+
def __init__(self, raw, frames, tx_id):
5+
self.raw = raw
56
self.frames = frames
67
self.tx_id = tx_id
78
self.data_bytes = []

obd/protocols/protocol.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@
1414

1515

1616
class Protocol(object):
17+
18+
PRIMARY_ECU = None
19+
1720
def __init__(self, baud=38400):
18-
self.baud = baud
21+
self.baud = baud
1922

2023

2124
def __call__(self, raw):
@@ -44,7 +47,7 @@ def __call__(self, raw):
4447
messages = []
4548

4649
for ecu in ecus:
47-
message = Message(ecus[ecu], ecu)
50+
message = Message(raw, ecus[ecu], ecu)
4851
# subclass function to assemble frames into data_bytes
4952
self.parse_message(message)
5053
messages.append(message)

obd/protocols/protocol_can.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
class CANProtocol(Protocol):
99

10+
PRIMARY_ECU = 0
11+
1012
def __init__(self, baud, id_bits):
1113
Protocol.__init__(self, baud)
1214
self.id_bits = id_bits

obd/protocols/protocol_legacy.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
class LegacyProtocol(Protocol):
99

10+
PRIMARY_ECU = 0x10
11+
1012
def __init__(self, baud):
1113
Protocol.__init__(self, baud)
1214

obd/utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ def ascii_to_bytes(a):
9696
b.append(int(a[i:i+2], 16))
9797
return b
9898

99+
def strip():
100+
return "".join(s.split())
101+
99102
def unhex(_hex):
100103
_hex = "0" if _hex == "" else _hex
101104
return int(_hex, 16)

0 commit comments

Comments
 (0)