summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--obd2-lib/Android.mk31
-rw-r--r--obd2-lib/AndroidManifest.xml21
-rw-r--r--obd2-lib/src/com/android/car/obd2/IntegerArrayStream.java95
-rw-r--r--obd2-lib/src/com/android/car/obd2/Obd2Command.java157
-rw-r--r--obd2-lib/src/com/android/car/obd2/Obd2Connection.java247
-rw-r--r--obd2-lib/src/com/android/car/obd2/commands/AmbientAirTemperature.java36
-rw-r--r--obd2-lib/src/com/android/car/obd2/commands/EngineOilTemperature.java34
-rw-r--r--tests/obd2_test/Android.mk39
-rw-r--r--tests/obd2_test/AndroidManifest.xml28
-rw-r--r--tests/obd2_test/src/com/android/car/obd2/test/IntegerArrayStreamTest.java62
-rw-r--r--tests/obd2_test/src/com/android/car/obd2/test/MockObd2UnderlyingTransport.java88
-rw-r--r--tests/obd2_test/src/com/android/car/obd2/test/Obd2ConnectionTest.java128
12 files changed, 966 insertions, 0 deletions
diff --git a/obd2-lib/Android.mk b/obd2-lib/Android.mk
new file mode 100644
index 00000000..544dfc14
--- /dev/null
+++ b/obd2-lib/Android.mk
@@ -0,0 +1,31 @@
1# Copyright (C) 2017 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14#
15#
16
17#disble build in PDK, missing aidl import breaks build
18ifneq ($(TARGET_BUILD_PDK),true)
19
20LOCAL_PATH:= $(call my-dir)
21
22include $(CLEAR_VARS)
23
24LOCAL_SRC_FILES := $(call all-java-files-under, src)
25
26LOCAL_MODULE := com.android.car.obd2
27LOCAL_JAVA_LANGUAGE_VERSION := 1.8
28
29include $(BUILD_STATIC_JAVA_LIBRARY)
30
31endif #TARGET_BUILD_PDK
diff --git a/obd2-lib/AndroidManifest.xml b/obd2-lib/AndroidManifest.xml
new file mode 100644
index 00000000..ea69d059
--- /dev/null
+++ b/obd2-lib/AndroidManifest.xml
@@ -0,0 +1,21 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (C) 2017 The Android Open Source Project
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15-->
16
17<manifest xmlns:android="http://schemas.android.com/apk/res/android"
18 xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
19 package="com.android.car.obd2" >
20 <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="23" />
21</manifest>
diff --git a/obd2-lib/src/com/android/car/obd2/IntegerArrayStream.java b/obd2-lib/src/com/android/car/obd2/IntegerArrayStream.java
new file mode 100644
index 00000000..56f64f77
--- /dev/null
+++ b/obd2-lib/src/com/android/car/obd2/IntegerArrayStream.java
@@ -0,0 +1,95 @@
1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.car.obd2;
18
19import java.util.function.Function;
20
21/**
22 * A wrapper over an int[] that offers a moving offset into the array, allowing for sequential
23 * consumption of the array data
24 */
25public class IntegerArrayStream {
26 private final int[] mData;
27 private int mIndex;
28
29 public IntegerArrayStream(int[] data) {
30 mData = data;
31 mIndex = 0;
32 }
33
34 public int peek() {
35 return mData[mIndex];
36 }
37
38 public int consume() {
39 return mData[mIndex++];
40 }
41
42 public int residualLength() {
43 return mData.length - mIndex;
44 }
45
46 public boolean hasAtLeast(int n) {
47 return residualLength() >= n;
48 }
49
50 public <T> T hasAtLeast(int n, Function<IntegerArrayStream, T> ifTrue) {
51 return hasAtLeast(n, ifTrue, null);
52 }
53
54 public <T> T hasAtLeast(
55 int n,
56 Function<IntegerArrayStream, T> ifTrue,
57 Function<IntegerArrayStream, T> ifFalse) {
58 if (hasAtLeast(n)) {
59 return ifTrue.apply(this);
60 } else {
61 if (ifFalse != null) {
62 return ifFalse.apply(this);
63 } else {
64 return null;
65 }
66 }
67 }
68
69 /**
70 * Validates the content of this stream against an expected data-set.
71 *
72 * <p>If any element of values causes a mismatch, that element will not be consumed and this
73 * method will return false. All elements that do match are consumed from the stream.
74 *
75 * <p>For instance, given a stream with {1,2,3,4}, a call of expect(1,2,5) will consume 1 and 2,
76 * will return false, and stream.peek() will return 3 since it is the first element that did not
77 * match and was not consumed.
78 *
79 * @param values The values to compare this stream's elements against.
80 * @return true if all elements of values match this stream, false otherwise.
81 */
82 public boolean expect(int... values) {
83 if (!hasAtLeast(values.length)) {
84 return false;
85 }
86 for (int value : values) {
87 if (value != peek()) {
88 return false;
89 } else {
90 consume();
91 }
92 }
93 return true;
94 }
95}
diff --git a/obd2-lib/src/com/android/car/obd2/Obd2Command.java b/obd2-lib/src/com/android/car/obd2/Obd2Command.java
new file mode 100644
index 00000000..3cbbf58e
--- /dev/null
+++ b/obd2-lib/src/com/android/car/obd2/Obd2Command.java
@@ -0,0 +1,157 @@
1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.car.obd2;
18
19import com.android.car.obd2.commands.AmbientAirTemperature;
20import com.android.car.obd2.commands.EngineOilTemperature;
21import java.io.IOException;
22import java.util.HashMap;
23import java.util.Optional;
24import java.util.Set;
25
26/**
27 * Base class of OBD2 command objects that query a "vehicle" and return an individual data point
28 * represented as a Java type.
29 *
30 * @param <ValueType> The Java type that represents the value of this command's output.
31 */
32public abstract class Obd2Command<ValueType> {
33
34 /**
35 * Abstract representation of an object whose job it is to receive the bytes read from the OBD2
36 * connection and return a Java representation of a command's value.
37 *
38 * @param <ValueType>
39 */
40 public interface OutputSemanticHandler<ValueType> {
41 int getPid();
42
43 Optional<ValueType> consume(IntegerArrayStream data);
44 }
45
46 public static final int LIVE_FRAME = 1;
47 public static final int FREEZE_FRAME = 2;
48
49 private static final HashMap<Integer, OutputSemanticHandler<Integer>>
50 SUPPORTED_INTEGER_COMMANDS = new HashMap<>();
51 private static final HashMap<Integer, OutputSemanticHandler<Float>> SUPPORTED_FLOAT_COMMANDS =
52 new HashMap<>();
53
54 private static void addSupportedIntegerCommand(
55 OutputSemanticHandler<Integer> integerOutputSemanticHandler) {
56 SUPPORTED_INTEGER_COMMANDS.put(
57 integerOutputSemanticHandler.getPid(), integerOutputSemanticHandler);
58 }
59
60 private static void addSupportedFloatCommand(
61 OutputSemanticHandler<Float> floatOutputSemanticHandler) {
62 SUPPORTED_FLOAT_COMMANDS.put(
63 floatOutputSemanticHandler.getPid(), floatOutputSemanticHandler);
64 }
65
66 public static Set<Integer> getSupportedIntegerCommands() {
67 return SUPPORTED_INTEGER_COMMANDS.keySet();
68 }
69
70 public static Set<Integer> getSupportedFloatCommands() {
71 return SUPPORTED_FLOAT_COMMANDS.keySet();
72 }
73
74 public static OutputSemanticHandler<Integer> getIntegerCommand(int pid) {
75 return SUPPORTED_INTEGER_COMMANDS.get(pid);
76 }
77
78 public static OutputSemanticHandler<Float> getFloatCommand(int pid) {
79 return SUPPORTED_FLOAT_COMMANDS.get(pid);
80 }
81
82 static {
83 addSupportedFloatCommand(new AmbientAirTemperature());
84 addSupportedIntegerCommand(new EngineOilTemperature());
85 }
86
87 protected final int mMode;
88 protected final OutputSemanticHandler<ValueType> mSemanticHandler;
89
90 Obd2Command(int mode, OutputSemanticHandler<ValueType> semanticHandler) {
91 mMode = mode;
92 mSemanticHandler = semanticHandler;
93 }
94
95 public abstract Optional<ValueType> run(Obd2Connection connection) throws Exception;
96
97 public static final <T> LiveFrameCommand<T> getLiveFrameCommand(OutputSemanticHandler handler) {
98 return new LiveFrameCommand<>(handler);
99 }
100
101 public static final <T> FreezeFrameCommand<T> getFreezeFrameCommand(
102 OutputSemanticHandler handler, int frameId) {
103 return new FreezeFrameCommand<>(handler, frameId);
104 }
105
106 /**
107 * An OBD2 command that returns live frame data.
108 *
109 * @param <ValueType> The Java type that represents the command's result type.
110 */
111 public static class LiveFrameCommand<ValueType> extends Obd2Command<ValueType> {
112 private static final int RESPONSE_MARKER = 0x41;
113
114 LiveFrameCommand(OutputSemanticHandler<ValueType> semanticHandler) {
115 super(LIVE_FRAME, semanticHandler);
116 }
117
118 public Optional<ValueType> run(Obd2Connection connection)
119 throws IOException, InterruptedException {
120 String command = String.format("%02X%02X", mMode, mSemanticHandler.getPid());
121 int[] data = connection.run(command);
122 IntegerArrayStream stream = new IntegerArrayStream(data);
123 if (stream.expect(RESPONSE_MARKER, mSemanticHandler.getPid())) {
124 return mSemanticHandler.consume(stream);
125 }
126 return Optional.empty();
127 }
128 }
129
130 /**
131 * An OBD2 command that returns freeze frame data.
132 *
133 * @param <ValueType> The Java type that represents the command's result type.
134 */
135 public static class FreezeFrameCommand<ValueType> extends Obd2Command<ValueType> {
136 private static final int RESPONSE_MARKER = 0x2;
137
138 private int mFrameId;
139
140 FreezeFrameCommand(OutputSemanticHandler<ValueType> semanticHandler, int frameId) {
141 super(FREEZE_FRAME, semanticHandler);
142 mFrameId = frameId;
143 }
144
145 public Optional<ValueType> run(Obd2Connection connection)
146 throws IOException, InterruptedException {
147 String command =
148 String.format("%02X%02X %02X", mMode, mSemanticHandler.getPid(), mFrameId);
149 int[] data = connection.run(command);
150 IntegerArrayStream stream = new IntegerArrayStream(data);
151 if (stream.expect(RESPONSE_MARKER, mSemanticHandler.getPid(), mFrameId)) {
152 return mSemanticHandler.consume(stream);
153 }
154 return Optional.empty();
155 }
156 }
157}
diff --git a/obd2-lib/src/com/android/car/obd2/Obd2Connection.java b/obd2-lib/src/com/android/car/obd2/Obd2Connection.java
new file mode 100644
index 00000000..62b070a0
--- /dev/null
+++ b/obd2-lib/src/com/android/car/obd2/Obd2Connection.java
@@ -0,0 +1,247 @@
1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.car.obd2;
18
19import android.util.Log;
20import java.io.IOException;
21import java.io.InputStream;
22import java.io.OutputStream;
23import java.util.HashSet;
24import java.util.Objects;
25import java.util.Set;
26
27/** This class represents a connection between Java code and a "vehicle" that talks OBD2. */
28public class Obd2Connection {
29
30 /**
31 * The transport layer that moves OBD2 requests from us to the remote entity and viceversa. It
32 * is possible for this to be USB, Bluetooth, or just as simple as a pty for a simulator.
33 */
34 public interface UnderlyingTransport {
35 String getAddress();
36
37 boolean reconnect();
38
39 InputStream getInputStream();
40
41 OutputStream getOutputStream();
42 }
43
44 private final UnderlyingTransport mConnection;
45
46 private static final String[] initCommands =
47 new String[] {"ATD", "ATZ", "AT E0", "AT L0", "AT S0", "AT H0", "AT SP 0"};
48
49 public Obd2Connection(UnderlyingTransport connection) {
50 mConnection = Objects.requireNonNull(connection);
51 runInitCommands();
52 }
53
54 public String getAddress() {
55 return mConnection.getAddress();
56 }
57
58 private void runInitCommands() {
59 for (final String initCommand : initCommands) {
60 try {
61 runImpl(initCommand);
62 } catch (IOException | InterruptedException e) {
63 }
64 }
65 }
66
67 public boolean reconnect() {
68 if (!mConnection.reconnect()) return false;
69 runInitCommands();
70 return true;
71 }
72
73 static int toDigitValue(char c) {
74 if ((c >= '0') && (c <= '9')) return c - '0';
75 switch (c) {
76 case 'a':
77 case 'A':
78 return 10;
79 case 'b':
80 case 'B':
81 return 11;
82 case 'c':
83 case 'C':
84 return 12;
85 case 'd':
86 case 'D':
87 return 13;
88 case 'e':
89 case 'E':
90 return 14;
91 case 'f':
92 case 'F':
93 return 15;
94 default:
95 throw new IllegalArgumentException(c + " is not a valid hex digit");
96 }
97 }
98
99 int[] toHexValues(String buffer) {
100 int[] values = new int[buffer.length() / 2];
101 for (int i = 0; i < values.length; ++i) {
102 values[i] =
103 16 * toDigitValue(buffer.charAt(2 * i))
104 + toDigitValue(buffer.charAt(2 * i + 1));
105 }
106 return values;
107 }
108
109 private String runImpl(String command) throws IOException, InterruptedException {
110 InputStream in = Objects.requireNonNull(mConnection.getInputStream());
111 OutputStream out = Objects.requireNonNull(mConnection.getOutputStream());
112
113 out.write((command + "\r").getBytes());
114 out.flush();
115
116 StringBuilder response = new StringBuilder();
117 while (true) {
118 int value = in.read();
119 if (value < 0) continue;
120 char c = (char) value;
121 // this is the prompt, stop here
122 if (c == '>') break;
123 if (c == '\r' || c == '\n' || c == ' ' || c == '\t' || c == '.') continue;
124 response.append(c);
125 }
126
127 String responseValue = response.toString();
128 return responseValue;
129 }
130
131 String removeSideData(String response, String... patterns) {
132 for (String pattern : patterns) {
133 if (response.contains(pattern)) response = response.replaceAll(pattern, "");
134 }
135 return response;
136 }
137
138 public int[] run(String command) throws IOException, InterruptedException {
139 String responseValue = runImpl(command);
140 String originalResponseValue = responseValue;
141 if (responseValue.startsWith(command))
142 responseValue = responseValue.substring(command.length());
143 //TODO(egranata): should probably handle these intelligently
144 responseValue =
145 removeSideData(
146 responseValue,
147 "SEARCHING",
148 "ERROR",
149 "BUS INIT",
150 "BUSINIT",
151 "BUS ERROR",
152 "BUSERROR",
153 "STOPPED");
154 if (responseValue.equals("OK")) return new int[] {1};
155 if (responseValue.equals("?")) return new int[] {0};
156 if (responseValue.equals("NODATA")) return new int[] {};
157 if (responseValue.equals("UNABLETOCONNECT")) throw new IOException("connection failure");
158 try {
159 return toHexValues(responseValue);
160 } catch (IllegalArgumentException e) {
161 Log.e(
162 "OBD2",
163 String.format(
164 "conversion error: command: '%s', original response: '%s'"
165 + ", processed response: '%s'",
166 command, originalResponseValue, responseValue));
167 throw e;
168 }
169 }
170
171 static class FourByteBitSet {
172 private static final int[] masks =
173 new int[] {
174 0b0000_0001,
175 0b0000_0010,
176 0b0000_0100,
177 0b0000_1000,
178 0b0001_0000,
179 0b0010_0000,
180 0b0100_0000,
181 0b1000_0000
182 };
183
184 private final byte mByte0;
185 private final byte mByte1;
186 private final byte mByte2;
187 private final byte mByte3;
188
189 FourByteBitSet(byte b0, byte b1, byte b2, byte b3) {
190 mByte0 = b0;
191 mByte1 = b1;
192 mByte2 = b2;
193 mByte3 = b3;
194 }
195
196 private byte getByte(int index) {
197 switch (index) {
198 case 0:
199 return mByte0;
200 case 1:
201 return mByte1;
202 case 2:
203 return mByte2;
204 case 3:
205 return mByte3;
206 default:
207 throw new IllegalArgumentException(index + " is not a valid byte index");
208 }
209 }
210
211 private boolean getBit(byte b, int index) {
212 if (index < 0 || index >= masks.length)
213 throw new IllegalArgumentException(index + " is not a valid bit index");
214 return 0 != (b & masks[index]);
215 }
216
217 public boolean getBit(int b, int index) {
218 return getBit(getByte(b), index);
219 }
220 }
221
222 public Set<Integer> getSupportedPIDs() throws IOException, InterruptedException {
223 Set<Integer> result = new HashSet<>();
224 String[] pids = new String[] {"0100", "0120", "0140", "0160"};
225 int basePid = 0;
226 for (String pid : pids) {
227 int[] responseData = run(pid);
228 if (responseData.length >= 6) {
229 byte byte0 = (byte) (responseData[2] & 0xFF);
230 byte byte1 = (byte) (responseData[3] & 0xFF);
231 byte byte2 = (byte) (responseData[4] & 0xFF);
232 byte byte3 = (byte) (responseData[5] & 0xFF);
233 FourByteBitSet fourByteBitSet = new FourByteBitSet(byte0, byte1, byte2, byte3);
234 for (int byteIndex = 0; byteIndex < 4; ++byteIndex) {
235 for (int bitIndex = 7; bitIndex >= 0; --bitIndex) {
236 if (fourByteBitSet.getBit(byteIndex, bitIndex)) {
237 result.add(basePid + 8 * byteIndex + 7 - bitIndex);
238 }
239 }
240 }
241 }
242 basePid += 0x20;
243 }
244
245 return result;
246 }
247}
diff --git a/obd2-lib/src/com/android/car/obd2/commands/AmbientAirTemperature.java b/obd2-lib/src/com/android/car/obd2/commands/AmbientAirTemperature.java
new file mode 100644
index 00000000..30108468
--- /dev/null
+++ b/obd2-lib/src/com/android/car/obd2/commands/AmbientAirTemperature.java
@@ -0,0 +1,36 @@
1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.car.obd2.commands;
18
19import com.android.car.obd2.IntegerArrayStream;
20import com.android.car.obd2.Obd2Command;
21import java.util.Optional;
22
23public class AmbientAirTemperature implements Obd2Command.OutputSemanticHandler<Float> {
24 @Override
25 public int getPid() {
26 return 0x46;
27 }
28
29 @Override
30 public Optional<Float> consume(IntegerArrayStream data) {
31 return data.hasAtLeast(
32 1,
33 theData -> Optional.of(theData.consume() * (100.0f / 255.0f)),
34 theData -> Optional.empty());
35 }
36}
diff --git a/obd2-lib/src/com/android/car/obd2/commands/EngineOilTemperature.java b/obd2-lib/src/com/android/car/obd2/commands/EngineOilTemperature.java
new file mode 100644
index 00000000..9db714cb
--- /dev/null
+++ b/obd2-lib/src/com/android/car/obd2/commands/EngineOilTemperature.java
@@ -0,0 +1,34 @@
1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.car.obd2.commands;
18
19import com.android.car.obd2.IntegerArrayStream;
20import com.android.car.obd2.Obd2Command;
21import java.util.Optional;
22
23public class EngineOilTemperature implements Obd2Command.OutputSemanticHandler<Integer> {
24 @Override
25 public int getPid() {
26 return 0x5C;
27 }
28
29 @Override
30 public Optional<Integer> consume(IntegerArrayStream data) {
31 return data.hasAtLeast(
32 1, theData -> Optional.of(theData.consume() - 40), theData -> Optional.empty());
33 }
34}
diff --git a/tests/obd2_test/Android.mk b/tests/obd2_test/Android.mk
new file mode 100644
index 00000000..198a69ac
--- /dev/null
+++ b/tests/obd2_test/Android.mk
@@ -0,0 +1,39 @@
1# Copyright (C) 2017 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14#
15#
16
17LOCAL_PATH:= $(call my-dir)
18
19include $(CLEAR_VARS)
20
21LOCAL_SRC_FILES := $(call all-java-files-under, src)
22
23LOCAL_PACKAGE_NAME := com.android.car.obd2.test
24
25LOCAL_CERTIFICATE := platform
26
27LOCAL_MODULE_TAGS := tests
28
29# When built explicitly put it in the data partition
30LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
31
32LOCAL_PROGUARD_ENABLED := disabled
33
34LOCAL_STATIC_JAVA_LIBRARIES += com.android.car.obd2 \
35 android-support-test \
36
37LOCAL_JAVA_LIBRARIES := android.car android.test.runner
38
39include $(BUILD_PACKAGE)
diff --git a/tests/obd2_test/AndroidManifest.xml b/tests/obd2_test/AndroidManifest.xml
new file mode 100644
index 00000000..0de21a49
--- /dev/null
+++ b/tests/obd2_test/AndroidManifest.xml
@@ -0,0 +1,28 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (C) 2017 The Android Open Source Project
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15-->
16
17<manifest xmlns:android="http://schemas.android.com/apk/res/android"
18 package="com.android.car.obd2.test"
19 android:sharedUserId="android.uid.system" >
20
21 <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
22 android:targetPackage="com.android.car.obd2.test"
23 android:label="Tests for OBD2 APIs"/>
24
25 <application android:label="OBD2 Tests">
26 <uses-library android:name="android.test.runner" />
27 </application>
28</manifest>
diff --git a/tests/obd2_test/src/com/android/car/obd2/test/IntegerArrayStreamTest.java b/tests/obd2_test/src/com/android/car/obd2/test/IntegerArrayStreamTest.java
new file mode 100644
index 00000000..3c3b1e6e
--- /dev/null
+++ b/tests/obd2_test/src/com/android/car/obd2/test/IntegerArrayStreamTest.java
@@ -0,0 +1,62 @@
1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.car.obd2.test;
18
19import static org.junit.Assert.*;
20
21import com.android.car.obd2.IntegerArrayStream;
22import org.junit.Test;
23
24/** Tests for IntegerArrayStream */
25public class IntegerArrayStreamTest {
26 private static int[] DATA_SET = new int[] {1, 2, 3, 4, 5, 6};
27
28 @Test
29 public void testPeekConsume() {
30 IntegerArrayStream stream = new IntegerArrayStream(DATA_SET);
31 assertEquals(1, stream.peek());
32 assertEquals(1, stream.consume());
33 assertEquals(2, stream.peek());
34 assertEquals(2, stream.consume());
35 }
36
37 @Test
38 public void testResidualLength() {
39 IntegerArrayStream stream = new IntegerArrayStream(DATA_SET);
40 assertEquals(DATA_SET.length, stream.residualLength());
41 stream.consume();
42 assertEquals(DATA_SET.length - 1, stream.residualLength());
43 }
44
45 @Test
46 public void testHasAtLeast() {
47 IntegerArrayStream stream = new IntegerArrayStream(DATA_SET);
48 assertTrue(stream.hasAtLeast(1));
49 assertTrue(stream.hasAtLeast(DATA_SET.length));
50 assertFalse(stream.hasAtLeast(100));
51 assertTrue(stream.hasAtLeast(1, theStream -> true));
52 assertFalse(stream.hasAtLeast(100, theStream -> true, theStream -> false));
53 }
54
55 @Test
56 public void testExpect() {
57 IntegerArrayStream stream = new IntegerArrayStream(DATA_SET);
58 assertTrue(stream.expect(1, 2, 3));
59 assertFalse(stream.expect(4, 6));
60 assertEquals(5, stream.peek());
61 }
62}
diff --git a/tests/obd2_test/src/com/android/car/obd2/test/MockObd2UnderlyingTransport.java b/tests/obd2_test/src/com/android/car/obd2/test/MockObd2UnderlyingTransport.java
new file mode 100644
index 00000000..1b51afb1
--- /dev/null
+++ b/tests/obd2_test/src/com/android/car/obd2/test/MockObd2UnderlyingTransport.java
@@ -0,0 +1,88 @@
1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.car.obd2.test;
18
19import com.android.car.obd2.IntegerArrayStream;
20import com.android.car.obd2.Obd2Connection;
21import java.io.EOFException;
22import java.io.IOException;
23import java.io.InputStream;
24import java.io.OutputStream;
25
26public class MockObd2UnderlyingTransport implements Obd2Connection.UnderlyingTransport {
27 private class MockInputStream extends InputStream {
28 private final IntegerArrayStream mStream;
29
30 MockInputStream(int... data) {
31 mStream = new IntegerArrayStream(data);
32 }
33
34 @Override
35 public int read() throws IOException {
36 if (mStream.hasAtLeast(1)) return mStream.consume();
37 else throw new EOFException();
38 }
39 }
40
41 private class MockOutputStream extends OutputStream {
42 private final IntegerArrayStream mStream;
43
44 MockOutputStream(int... data) {
45 mStream = new IntegerArrayStream(data);
46 }
47
48 @Override
49 public void write(int b) throws IOException {
50 if (mStream.hasAtLeast(1)) {
51 int expected = mStream.consume();
52 if (expected != b) {
53 throw new IOException("data mismatch. expected: " + expected + ", seen: " + b);
54 }
55 } else {
56 throw new IOException("data write past expectation.");
57 }
58 }
59 }
60
61 private final MockInputStream mMockInput;
62 private final MockOutputStream mMockOutput;
63
64 public MockObd2UnderlyingTransport(int[] requestData, int[] responseData) {
65 mMockInput = new MockInputStream(responseData);
66 mMockOutput = new MockOutputStream(requestData);
67 }
68
69 @Override
70 public String getAddress() {
71 return MockObd2UnderlyingTransport.class.getSimpleName();
72 }
73
74 @Override
75 public boolean reconnect() {
76 return true;
77 }
78
79 @Override
80 public InputStream getInputStream() {
81 return mMockInput;
82 }
83
84 @Override
85 public OutputStream getOutputStream() {
86 return mMockOutput;
87 }
88}
diff --git a/tests/obd2_test/src/com/android/car/obd2/test/Obd2ConnectionTest.java b/tests/obd2_test/src/com/android/car/obd2/test/Obd2ConnectionTest.java
new file mode 100644
index 00000000..a5f06e15
--- /dev/null
+++ b/tests/obd2_test/src/com/android/car/obd2/test/Obd2ConnectionTest.java
@@ -0,0 +1,128 @@
1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.car.obd2.test;
18
19import static org.junit.Assert.*;
20
21import com.android.car.obd2.Obd2Command;
22import com.android.car.obd2.Obd2Connection;
23import java.util.ArrayList;
24import java.util.Arrays;
25import java.util.Optional;
26import org.junit.Test;
27
28public class Obd2ConnectionTest {
29 private static int[] stringsToIntArray(String... strings) {
30 ArrayList<Integer> arrayList = new ArrayList<>();
31 for (String string : strings) {
32 string.chars().forEach(arrayList::add);
33 }
34 return arrayList.stream().mapToInt(Integer::intValue).toArray();
35 }
36
37 private static int[] concatIntArrays(int[] array1, int[] array2) {
38 int[] newArray = Arrays.copyOf(array1, array1.length + array2.length);
39 System.arraycopy(array2, 0, newArray, array1.length, array2.length);
40 return newArray;
41 }
42
43 private static final String[] EXPECTED_INIT_COMMANDS =
44 new String[] {
45 "ATD\r", "ATZ\r", "AT E0\r", "AT L0\r", "AT S0\r", "AT H0\r", "AT SP 0\r"
46 };
47
48 private static final int AIR_TEMPERATURE_PID = 0x46;
49 private static final int ENGINE_OIL_TEMPERATURE_PID = 0x5C;
50
51 private static final String[] EXPECTED_AIR_TEMPERATURE_COMMANDS = new String[] {"0146\r"};
52 private static final String[] EXPECTED_ENGINE_OIL_TEMPERATURE_COMMANDS =
53 new String[] {"015C\r"};
54
55 private static final String OBD2_PROMPT = ">";
56
57 private static final String[] EXPECTED_INIT_RESPONSES =
58 new String[] {
59 OBD2_PROMPT,
60 OBD2_PROMPT,
61 OBD2_PROMPT,
62 OBD2_PROMPT,
63 OBD2_PROMPT,
64 OBD2_PROMPT,
65 OBD2_PROMPT
66 };
67
68 private static final String[] EXPECTED_AIR_TEMPERATURE_RESPONSES =
69 new String[] {"41 46 A1", OBD2_PROMPT};
70 private static final String[] EXPECTED_ENGINE_OIL_TEMPERATURE_RESPONSES =
71 new String[] {"41 5C 87", OBD2_PROMPT};
72
73 @Test
74 public void testEstablishConnection() {
75 MockObd2UnderlyingTransport transport =
76 new MockObd2UnderlyingTransport(
77 stringsToIntArray(EXPECTED_INIT_COMMANDS),
78 stringsToIntArray(EXPECTED_INIT_RESPONSES));
79 Obd2Connection obd2Connection = new Obd2Connection(transport);
80 }
81
82 @Test
83 public void testAmbientAirTemperature() {
84 MockObd2UnderlyingTransport transport =
85 new MockObd2UnderlyingTransport(
86 concatIntArrays(
87 stringsToIntArray(EXPECTED_INIT_COMMANDS),
88 stringsToIntArray(EXPECTED_AIR_TEMPERATURE_COMMANDS)),
89 concatIntArrays(
90 stringsToIntArray(EXPECTED_INIT_RESPONSES),
91 stringsToIntArray(EXPECTED_AIR_TEMPERATURE_RESPONSES)));
92 Obd2Connection obd2Connection = new Obd2Connection(transport);
93 Obd2Command<Float> airTemperatureCommand =
94 Obd2Command.getLiveFrameCommand(Obd2Command.getFloatCommand(AIR_TEMPERATURE_PID));
95 Optional<Float> airTemperature = Optional.empty();
96 try {
97 airTemperature = airTemperatureCommand.run(obd2Connection);
98 } catch (Exception e) {
99 assertTrue("airTemperature caused an exception: " + e, false);
100 }
101 assertTrue(airTemperature.isPresent());
102 assertEquals(63.137257, (double) airTemperature.get(), 0.001);
103 }
104
105 @Test
106 public void testEngineOilTemperature() {
107 MockObd2UnderlyingTransport transport =
108 new MockObd2UnderlyingTransport(
109 concatIntArrays(
110 stringsToIntArray(EXPECTED_INIT_COMMANDS),
111 stringsToIntArray(EXPECTED_ENGINE_OIL_TEMPERATURE_COMMANDS)),
112 concatIntArrays(
113 stringsToIntArray(EXPECTED_INIT_RESPONSES),
114 stringsToIntArray(EXPECTED_ENGINE_OIL_TEMPERATURE_RESPONSES)));
115 Obd2Connection obd2Connection = new Obd2Connection(transport);
116 Obd2Command<Integer> engineOilTemperatureCommand =
117 Obd2Command.getLiveFrameCommand(
118 Obd2Command.getIntegerCommand(ENGINE_OIL_TEMPERATURE_PID));
119 Optional<Integer> engineOilTemperature = Optional.empty();
120 try {
121 engineOilTemperature = engineOilTemperatureCommand.run(obd2Connection);
122 } catch (Exception e) {
123 assertTrue("engineOilTemperature caused an exception: " + e, false);
124 }
125 assertTrue(engineOilTemperature.isPresent());
126 assertEquals(95, (int) engineOilTemperature.get());
127 }
128}