Skip to content

Commit e12efdb

Browse files
author
Brendan Whitfield
committed
started writing tests for protocol system
1 parent e9af33b commit e12efdb

9 files changed

Lines changed: 180 additions & 54 deletions

File tree

obd/OBDCommand.py

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -61,34 +61,25 @@ def get_mode_int(self):
6161
def get_pid_int(self):
6262
return unhex(self.pid)
6363

64-
def compute(self, messages):
65-
66-
_bytes = []
67-
68-
if len(messages) == 1:
69-
_bytes = messages[0].data_bytes
70-
else:
71-
pass
72-
64+
def compute(self, message):
7365

7466
# create the response object with the raw data recieved
7567
r = Response(message)
7668

77-
# combine the bytes back into a hex string, excluding the header + mode + pid, and trailing checksum
78-
_bytes = "".join(lines[0][5:-1])
69+
# combine the bytes back into a hex string
70+
# TODO: rewrite decoders to handle raw byte arrays
71+
_data = ""
72+
for b in message.data_bytes:
73+
h = hex(b)[2:].upper()
74+
h = "0" + h if len(h) < 2 else h
75+
_data += h
7976

80-
if ("NODATA" not in _data) and isHex(_data):
77+
# constrain number of bytes in response
78+
if (self.bytes > 0): # zero bytes means flexible response
79+
_data = constrainHex(_data, self.bytes)
8180

82-
# constrain number of bytes in response
83-
if (self.bytes > 0): # zero bytes means flexible response
84-
_data = constrainHex(_data, self.bytes)
85-
86-
# decoded value into the response object
87-
r.set(self.decode(_data))
88-
89-
else:
90-
# not a parseable response
91-
debug("return data could not be decoded")
81+
# decoded value into the response object
82+
r.set(self.decode(_data))
9283

9384
return r
9485

obd/elm327.py

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,16 @@ class ELM327:
5959
def __init__(self, portname):
6060
"""Initializes port by resetting device and gettings supported PIDs. """
6161

62-
self.connected = False
63-
self.port = None
64-
self.protocol = None
62+
self.__connected = False
63+
self.__port = None
64+
self.__protocol = None
6565

6666
# ------------- open port -------------
6767

6868
debug("Opening serial port '%s'" % portname)
6969

7070
try:
71-
self.port = serial.Serial(portname, \
71+
self.__port = serial.Serial(portname, \
7272
baudrate = 38400, \
7373
parity = serial.PARITY_NONE, \
7474
stopbits = 1, \
@@ -132,12 +132,12 @@ def __init__(self, portname):
132132
self.__error("ELM responded with unknown protocol")
133133
return
134134

135-
self.protocol = _SUPPORTED_PROTOCOLS[r]()
135+
self.__protocol = _SUPPORTED_PROTOCOLS[r]()
136136

137137

138138
# ------------------------------- done -------------------------------
139139
debug("Connection successful")
140-
self.connected = True
140+
self.__connected = True
141141

142142

143143
def __error(self, msg=None):
@@ -148,42 +148,42 @@ def __error(self, msg=None):
148148
if msg is not None:
149149
debug(' ' + str(msg), True)
150150

151-
if self.port is not None:
152-
self.port.close()
151+
if self.__port is not None:
152+
self.__port.close()
153153

154-
self.connected = False
154+
self.__connected = False
155155

156156

157157
def get_port_name(self):
158-
return self.port.portstr if (self.port is not None) else "No Port"
158+
return self.__port.portstr if (self.__port is not None) else "No Port"
159159

160160

161161
def is_connected(self):
162-
return self.connected
162+
return self.__connected
163163

164164

165165
def close(self):
166166
""" Resets device and closes all associated filehandles"""
167167

168-
if (self.port != None) and self.connected:
168+
if (self.__port != None) and self.__connected:
169169
self.__write("ATZ")
170-
self.port.close()
170+
self.__port.close()
171171

172-
self.connected = False
173-
self.port = None
174-
self.protocol = None
172+
self.__connected = False
173+
self.__port = None
174+
self.__protocol = None
175175

176176

177177
def send_and_parse(self, cmd, delay=None):
178178

179179
r = self.send(cmd, delay)
180180

181-
messages = self.protocol(r) # parses string into list of messages
181+
messages = self.__protocol(r) # parses string into list of messages
182182

183183
# if more than one ECUs have responded, pick the primary
184184
# TODO: add support for more ECU types
185185
if len(messages) > 1:
186-
messages = filter(lambda m: m.tx_id == self.protocol.PRIMARY_ECU, messages)
186+
messages = filter(lambda m: m.tx_id == self.__protocol.PRIMARY_ECU, messages)
187187

188188
return messages[0]
189189

@@ -202,11 +202,12 @@ def send(self, cmd, delay=None):
202202

203203
# sends the hex string to the port
204204
def __write(self, cmd):
205-
if self.port:
205+
206+
if self.__port:
206207
cmd += "\r\n" # terminate
207-
self.port.flushOutput()
208-
self.port.flushInput()
209-
self.port.write(cmd)
208+
self.__port.flushOutput()
209+
self.__port.flushInput()
210+
self.__port.write(cmd)
210211
debug("write: " + repr(cmd))
211212
else:
212213
debug("cannot perform write() when unconnected", True)
@@ -219,9 +220,9 @@ def __read(self):
219220
attempts = 2
220221
result = ""
221222

222-
if self.port:
223+
if self.__port:
223224
while True:
224-
c = self.port.read(1)
225+
c = self.__port.read(1)
225226

226227
# if nothing was recieved
227228
if not c:

obd/protocols/protocol.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
########################################################################
3131

3232
import re
33-
from obd.utils import ascii_to_bytes
33+
from obd.utils import ascii_to_bytes, isHex
3434
from obd.debug import debug
3535

3636

@@ -60,6 +60,15 @@ def __init__(self, frames, tx_id):
6060
self.tx_id = tx_id
6161
self.data_bytes = []
6262

63+
def __eq__(self, other):
64+
if isinstance(other, Message):
65+
for attr in ["frames", "tx_id", "data_bytes"]:
66+
if getattr(self, attr) != getattr(other, attr):
67+
return False
68+
return True
69+
else:
70+
return False
71+
6372

6473

6574

@@ -87,6 +96,9 @@ def __call__(self, raw):
8796
# ditch spaces
8897
lines = [line.replace(' ', '') for line in lines]
8998

99+
# ditch frames without valid hex (trashes "NO DATA", etc...)
100+
lines = filter(isHex, lines)
101+
90102
frames = []
91103
for line in lines:
92104
# subclass function to parse the lines into Frames

obd/protocols/protocol_can.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ def create_frame(self, raw):
5555
frame = Frame(raw)
5656
raw_bytes = ascii_to_bytes(raw)
5757

58+
print raw
59+
print raw_bytes
60+
5861
# read header information
5962
if self.id_bits == 11:
6063
frame.priority = raw_bytes[2] & 0x0F # always 7

obd/protocols/protocol_legacy.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ def create_frame(self, raw):
4444
frame = Frame(raw)
4545
raw_bytes = ascii_to_bytes(raw)
4646

47+
if len(raw_bytes) < 5:
48+
return None
49+
4750
frame.data_bytes = raw_bytes[3:-1] # exclude trailing checksum (handled by ELM adapter)
4851

4952
# read header information

obd/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,14 @@ class Unit:
6262

6363

6464
class Response():
65-
def __init__(self, raw_data=""):
65+
def __init__(self, raw_data=None):
6666
self.value = None
6767
self.unit = Unit.NONE
6868
self.raw_data = raw_data
6969
self.time = time.time()
7070

7171
def is_null(self):
72-
return (len(self.raw_data) == 0) or (self.value == None)
72+
return (self.raw_data == None) or (self.value == None)
7373

7474
def set(self, decode):
7575
self.value = decode[0]

tests/test_OBD.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from obd.utils import Response
44
from obd.commands import OBDCommand
55
from obd.decoders import noop
6+
from obd.protocols import SAE_J1850_PWM
67

78

89
def test_is_connected():
@@ -12,11 +13,12 @@ def test_is_connected():
1213
# todo
1314

1415

16+
# TODO: rewrite for new protocol architecture
1517
def test_query():
1618
# we don't need an actual serial connection
1719
o = obd.OBD("/dev/null")
1820
# forge our own command, to control the output
19-
cmd = OBDCommand("", "", "01", "23", 2, noop)
21+
cmd = OBDCommand("TEST", "Test command", "01", "23", 2, noop)
2022

2123
# forge IO from the car by overwriting the read/write functions
2224

@@ -28,9 +30,11 @@ def write(cmd):
2830
toCar[0] = cmd
2931

3032
o.is_connected = lambda *args: True
31-
o.port.port = True
32-
o.port._OBDPort__write = write
33-
o.port._OBDPort__read = lambda *args: fromCar
33+
o.port.is_connected = lambda *args: True
34+
35+
o.port._ELM327__protocol = SAE_J1850_PWM()
36+
o.port._ELM327__write = write
37+
o.port._ELM327__read = lambda *args: fromCar
3438

3539
# make sure unsupported commands don't write ------------------------------
3640
fromCar = "48 6B 10 41 23 AB CD 10\r\r"
@@ -105,6 +109,5 @@ def write(cmd):
105109
assert r.raw_data == fromCar
106110
assert r.is_null()
107111

108-
109112
def test_load_commands():
110113
pass

tests/test_OBDCommand.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ def test_clone():
3636
assert cmd.supported == cmd.supported
3737

3838

39+
# TODO: rewrite these for new commands accepting messages (rather than strings)
40+
"""
41+
3942
def test_data_stripping():
4043
# name description mode cmd bytes decoder
4144
cmd = OBDCommand("Test", "example OBD command", "01", "00", 2, noop)
@@ -58,3 +61,5 @@ def test_data_length():
5861
assert r.value == "0123"
5962
r = cmd.compute("48 6B 10 41 00 01 10\r\n")
6063
assert r.value == "0100"
64+
65+
"""

0 commit comments

Comments
 (0)