diff options
-rw-r--r-- | obd2-lib/Android.mk | 31 | ||||
-rw-r--r-- | obd2-lib/AndroidManifest.xml | 21 | ||||
-rw-r--r-- | obd2-lib/src/com/android/car/obd2/IntegerArrayStream.java | 95 | ||||
-rw-r--r-- | obd2-lib/src/com/android/car/obd2/Obd2Command.java | 157 | ||||
-rw-r--r-- | obd2-lib/src/com/android/car/obd2/Obd2Connection.java | 247 | ||||
-rw-r--r-- | obd2-lib/src/com/android/car/obd2/commands/AmbientAirTemperature.java | 36 | ||||
-rw-r--r-- | obd2-lib/src/com/android/car/obd2/commands/EngineOilTemperature.java | 34 | ||||
-rw-r--r-- | tests/obd2_test/Android.mk | 39 | ||||
-rw-r--r-- | tests/obd2_test/AndroidManifest.xml | 28 | ||||
-rw-r--r-- | tests/obd2_test/src/com/android/car/obd2/test/IntegerArrayStreamTest.java | 62 | ||||
-rw-r--r-- | tests/obd2_test/src/com/android/car/obd2/test/MockObd2UnderlyingTransport.java | 88 | ||||
-rw-r--r-- | tests/obd2_test/src/com/android/car/obd2/test/Obd2ConnectionTest.java | 128 |
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 | ||
18 | ifneq ($(TARGET_BUILD_PDK),true) | ||
19 | |||
20 | LOCAL_PATH:= $(call my-dir) | ||
21 | |||
22 | include $(CLEAR_VARS) | ||
23 | |||
24 | LOCAL_SRC_FILES := $(call all-java-files-under, src) | ||
25 | |||
26 | LOCAL_MODULE := com.android.car.obd2 | ||
27 | LOCAL_JAVA_LANGUAGE_VERSION := 1.8 | ||
28 | |||
29 | include $(BUILD_STATIC_JAVA_LIBRARY) | ||
30 | |||
31 | endif #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 | |||
17 | package com.android.car.obd2; | ||
18 | |||
19 | import 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 | */ | ||
25 | public 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 | |||
17 | package com.android.car.obd2; | ||
18 | |||
19 | import com.android.car.obd2.commands.AmbientAirTemperature; | ||
20 | import com.android.car.obd2.commands.EngineOilTemperature; | ||
21 | import java.io.IOException; | ||
22 | import java.util.HashMap; | ||
23 | import java.util.Optional; | ||
24 | import 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 | */ | ||
32 | public 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 | |||
17 | package com.android.car.obd2; | ||
18 | |||
19 | import android.util.Log; | ||
20 | import java.io.IOException; | ||
21 | import java.io.InputStream; | ||
22 | import java.io.OutputStream; | ||
23 | import java.util.HashSet; | ||
24 | import java.util.Objects; | ||
25 | import java.util.Set; | ||
26 | |||
27 | /** This class represents a connection between Java code and a "vehicle" that talks OBD2. */ | ||
28 | public 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 | |||
17 | package com.android.car.obd2.commands; | ||
18 | |||
19 | import com.android.car.obd2.IntegerArrayStream; | ||
20 | import com.android.car.obd2.Obd2Command; | ||
21 | import java.util.Optional; | ||
22 | |||
23 | public 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 | |||
17 | package com.android.car.obd2.commands; | ||
18 | |||
19 | import com.android.car.obd2.IntegerArrayStream; | ||
20 | import com.android.car.obd2.Obd2Command; | ||
21 | import java.util.Optional; | ||
22 | |||
23 | public 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 | |||
17 | LOCAL_PATH:= $(call my-dir) | ||
18 | |||
19 | include $(CLEAR_VARS) | ||
20 | |||
21 | LOCAL_SRC_FILES := $(call all-java-files-under, src) | ||
22 | |||
23 | LOCAL_PACKAGE_NAME := com.android.car.obd2.test | ||
24 | |||
25 | LOCAL_CERTIFICATE := platform | ||
26 | |||
27 | LOCAL_MODULE_TAGS := tests | ||
28 | |||
29 | # When built explicitly put it in the data partition | ||
30 | LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS) | ||
31 | |||
32 | LOCAL_PROGUARD_ENABLED := disabled | ||
33 | |||
34 | LOCAL_STATIC_JAVA_LIBRARIES += com.android.car.obd2 \ | ||
35 | android-support-test \ | ||
36 | |||
37 | LOCAL_JAVA_LIBRARIES := android.car android.test.runner | ||
38 | |||
39 | include $(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 | |||
17 | package com.android.car.obd2.test; | ||
18 | |||
19 | import static org.junit.Assert.*; | ||
20 | |||
21 | import com.android.car.obd2.IntegerArrayStream; | ||
22 | import org.junit.Test; | ||
23 | |||
24 | /** Tests for IntegerArrayStream */ | ||
25 | public 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 | |||
17 | package com.android.car.obd2.test; | ||
18 | |||
19 | import com.android.car.obd2.IntegerArrayStream; | ||
20 | import com.android.car.obd2.Obd2Connection; | ||
21 | import java.io.EOFException; | ||
22 | import java.io.IOException; | ||
23 | import java.io.InputStream; | ||
24 | import java.io.OutputStream; | ||
25 | |||
26 | public 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 | |||
17 | package com.android.car.obd2.test; | ||
18 | |||
19 | import static org.junit.Assert.*; | ||
20 | |||
21 | import com.android.car.obd2.Obd2Command; | ||
22 | import com.android.car.obd2.Obd2Connection; | ||
23 | import java.util.ArrayList; | ||
24 | import java.util.Arrays; | ||
25 | import java.util.Optional; | ||
26 | import org.junit.Test; | ||
27 | |||
28 | public 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 | } | ||