summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'obd2-lib')
-rw-r--r--obd2-lib/src/com/android/car/obd2/IntegerArrayStream.java4
-rw-r--r--obd2-lib/src/com/android/car/obd2/Obd2Command.java2
-rw-r--r--obd2-lib/src/com/android/car/obd2/Obd2Connection.java93
-rw-r--r--obd2-lib/src/com/android/car/obd2/Obd2FreezeFrameGenerator.java177
-rw-r--r--obd2-lib/src/com/android/car/obd2/commands/RPM.java2
5 files changed, 271 insertions, 7 deletions
diff --git a/obd2-lib/src/com/android/car/obd2/IntegerArrayStream.java b/obd2-lib/src/com/android/car/obd2/IntegerArrayStream.java
index 56f64f77..4768cebc 100644
--- a/obd2-lib/src/com/android/car/obd2/IntegerArrayStream.java
+++ b/obd2-lib/src/com/android/car/obd2/IntegerArrayStream.java
@@ -43,6 +43,10 @@ public class IntegerArrayStream {
43 return mData.length - mIndex; 43 return mData.length - mIndex;
44 } 44 }
45 45
46 public boolean isEmpty() {
47 return residualLength() == 0;
48 }
49
46 public boolean hasAtLeast(int n) { 50 public boolean hasAtLeast(int n) {
47 return residualLength() >= n; 51 return residualLength() >= n;
48 } 52 }
diff --git a/obd2-lib/src/com/android/car/obd2/Obd2Command.java b/obd2-lib/src/com/android/car/obd2/Obd2Command.java
index e3366cfd..30fca0c1 100644
--- a/obd2-lib/src/com/android/car/obd2/Obd2Command.java
+++ b/obd2-lib/src/com/android/car/obd2/Obd2Command.java
@@ -172,7 +172,7 @@ public abstract class Obd2Command<ValueType> {
172 * @param <ValueType> The Java type that represents the command's result type. 172 * @param <ValueType> The Java type that represents the command's result type.
173 */ 173 */
174 public static class FreezeFrameCommand<ValueType> extends Obd2Command<ValueType> { 174 public static class FreezeFrameCommand<ValueType> extends Obd2Command<ValueType> {
175 private static final int RESPONSE_MARKER = 0x2; 175 private static final int RESPONSE_MARKER = 0x42;
176 176
177 private int mFrameId; 177 private int mFrameId;
178 178
diff --git a/obd2-lib/src/com/android/car/obd2/Obd2Connection.java b/obd2-lib/src/com/android/car/obd2/Obd2Connection.java
index 577b7989..f7a2d369 100644
--- a/obd2-lib/src/com/android/car/obd2/Obd2Connection.java
+++ b/obd2-lib/src/com/android/car/obd2/Obd2Connection.java
@@ -20,12 +20,16 @@ import android.util.Log;
20import java.io.IOException; 20import java.io.IOException;
21import java.io.InputStream; 21import java.io.InputStream;
22import java.io.OutputStream; 22import java.io.OutputStream;
23import java.util.ArrayList;
23import java.util.HashSet; 24import java.util.HashSet;
25import java.util.List;
24import java.util.Objects; 26import java.util.Objects;
25import java.util.Set; 27import java.util.Set;
26 28
27/** This class represents a connection between Java code and a "vehicle" that talks OBD2. */ 29/** This class represents a connection between Java code and a "vehicle" that talks OBD2. */
28public class Obd2Connection { 30public class Obd2Connection {
31 private static final String TAG = Obd2Connection.class.getSimpleName();
32 private static final boolean DBG = false;
29 33
30 /** 34 /**
31 * The transport layer that moves OBD2 requests from us to the remote entity and viceversa. It 35 * The transport layer that moves OBD2 requests from us to the remote entity and viceversa. It
@@ -116,6 +120,10 @@ public class Obd2Connection {
116 InputStream in = Objects.requireNonNull(mConnection.getInputStream()); 120 InputStream in = Objects.requireNonNull(mConnection.getInputStream());
117 OutputStream out = Objects.requireNonNull(mConnection.getOutputStream()); 121 OutputStream out = Objects.requireNonNull(mConnection.getOutputStream());
118 122
123 if (DBG) {
124 Log.i(TAG, "runImpl(" + command + ")");
125 }
126
119 out.write((command + "\r").getBytes()); 127 out.write((command + "\r").getBytes());
120 out.flush(); 128 out.flush();
121 129
@@ -131,6 +139,11 @@ public class Obd2Connection {
131 } 139 }
132 140
133 String responseValue = response.toString(); 141 String responseValue = response.toString();
142
143 if (DBG) {
144 Log.i(TAG, "runImpl() returned " + responseValue);
145 }
146
134 return responseValue; 147 return responseValue;
135 } 148 }
136 149
@@ -141,11 +154,30 @@ public class Obd2Connection {
141 return response; 154 return response;
142 } 155 }
143 156
157 String unpackLongFrame(String response) {
158 // long frames come back to us containing colon separated portions
159 if (response.indexOf(':') < 0) return response;
160
161 // remove everything until the first colon
162 response = response.substring(response.indexOf(':') + 1);
163
164 // then remove the <digit>: portions (sequential frame parts)
165 //TODO(egranata): maybe validate the sequence of digits is progressive
166 return response.replaceAll("[0-9]:", "");
167 }
168
144 public int[] run(String command) throws IOException, InterruptedException { 169 public int[] run(String command) throws IOException, InterruptedException {
145 String responseValue = runImpl(command); 170 String responseValue = runImpl(command);
146 String originalResponseValue = responseValue; 171 String originalResponseValue = responseValue;
147 if (responseValue.startsWith(command)) 172 String unspacedCommand = command.replaceAll(" ", "");
148 responseValue = responseValue.substring(command.length()); 173 if (responseValue.startsWith(unspacedCommand))
174 responseValue = responseValue.substring(unspacedCommand.length());
175 responseValue = unpackLongFrame(responseValue);
176
177 if (DBG) {
178 Log.i(TAG, "post-processed response " + responseValue);
179 }
180
149 //TODO(egranata): should probably handle these intelligently 181 //TODO(egranata): should probably handle these intelligently
150 responseValue = 182 responseValue =
151 removeSideData( 183 removeSideData(
@@ -161,11 +193,12 @@ public class Obd2Connection {
161 if (responseValue.equals("?")) return new int[] {0}; 193 if (responseValue.equals("?")) return new int[] {0};
162 if (responseValue.equals("NODATA")) return new int[] {}; 194 if (responseValue.equals("NODATA")) return new int[] {};
163 if (responseValue.equals("UNABLETOCONNECT")) throw new IOException("connection failure"); 195 if (responseValue.equals("UNABLETOCONNECT")) throw new IOException("connection failure");
196 if (responseValue.equals("CANERROR")) throw new IOException("CAN bus error");
164 try { 197 try {
165 return toHexValues(responseValue); 198 return toHexValues(responseValue);
166 } catch (IllegalArgumentException e) { 199 } catch (IllegalArgumentException e) {
167 Log.e( 200 Log.e(
168 "OBD2", 201 TAG,
169 String.format( 202 String.format(
170 "conversion error: command: '%s', original response: '%s'" 203 "conversion error: command: '%s', original response: '%s'"
171 + ", processed response: '%s'", 204 + ", processed response: '%s'",
@@ -228,7 +261,7 @@ public class Obd2Connection {
228 public Set<Integer> getSupportedPIDs() throws IOException, InterruptedException { 261 public Set<Integer> getSupportedPIDs() throws IOException, InterruptedException {
229 Set<Integer> result = new HashSet<>(); 262 Set<Integer> result = new HashSet<>();
230 String[] pids = new String[] {"0100", "0120", "0140", "0160"}; 263 String[] pids = new String[] {"0100", "0120", "0140", "0160"};
231 int basePid = 0; 264 int basePid = 1;
232 for (String pid : pids) { 265 for (String pid : pids) {
233 int[] responseData = run(pid); 266 int[] responseData = run(pid);
234 if (responseData.length >= 6) { 267 if (responseData.length >= 6) {
@@ -236,11 +269,19 @@ public class Obd2Connection {
236 byte byte1 = (byte) (responseData[3] & 0xFF); 269 byte byte1 = (byte) (responseData[3] & 0xFF);
237 byte byte2 = (byte) (responseData[4] & 0xFF); 270 byte byte2 = (byte) (responseData[4] & 0xFF);
238 byte byte3 = (byte) (responseData[5] & 0xFF); 271 byte byte3 = (byte) (responseData[5] & 0xFF);
272 if (DBG) {
273 Log.i(TAG, String.format("supported PID at base %d payload %02X%02X%02X%02X",
274 basePid, byte0, byte1, byte2, byte3));
275 }
239 FourByteBitSet fourByteBitSet = new FourByteBitSet(byte0, byte1, byte2, byte3); 276 FourByteBitSet fourByteBitSet = new FourByteBitSet(byte0, byte1, byte2, byte3);
240 for (int byteIndex = 0; byteIndex < 4; ++byteIndex) { 277 for (int byteIndex = 0; byteIndex < 4; ++byteIndex) {
241 for (int bitIndex = 7; bitIndex >= 0; --bitIndex) { 278 for (int bitIndex = 7; bitIndex >= 0; --bitIndex) {
242 if (fourByteBitSet.getBit(byteIndex, bitIndex)) { 279 if (fourByteBitSet.getBit(byteIndex, bitIndex)) {
243 result.add(basePid + 8 * byteIndex + 7 - bitIndex); 280 int command = basePid + 8 * byteIndex + 7 - bitIndex;
281 if (DBG) {
282 Log.i(TAG, "command " + command + " found supported");
283 }
284 result.add(command);
244 } 285 }
245 } 286 }
246 } 287 }
@@ -250,4 +291,46 @@ public class Obd2Connection {
250 291
251 return result; 292 return result;
252 } 293 }
294
295 String getDiagnosticTroubleCode(IntegerArrayStream source) {
296 final char[] components = new char[] {'P', 'C', 'B', 'U'};
297 final char[] firstDigits = new char[] {'0', '1', '2', '3'};
298 final char[] otherDigits =
299 new char[] {
300 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
301 };
302
303 StringBuilder builder = new StringBuilder(5);
304
305 int byte0 = source.consume();
306 int byte1 = source.consume();
307
308 int componentMask = (byte0 & 0xC0) >> 6;
309 int firstDigitMask = (byte0 & 0x30) >> 4;
310 int secondDigitMask = (byte0 & 0x0F);
311 int thirdDigitMask = (byte1 & 0xF0) >> 4;
312 int fourthDigitMask = (byte1 & 0x0F);
313
314 builder.append(components[componentMask]);
315 builder.append(firstDigits[firstDigitMask]);
316 builder.append(otherDigits[secondDigitMask]);
317 builder.append(otherDigits[thirdDigitMask]);
318 builder.append(otherDigits[fourthDigitMask]);
319
320 return builder.toString();
321 }
322
323 public List<String> getDiagnosticTroubleCodes() throws IOException, InterruptedException {
324 List<String> result = new ArrayList<>();
325 int[] response = run("03");
326 IntegerArrayStream stream = new IntegerArrayStream(response);
327 if (stream.isEmpty()) return result;
328 if (!stream.expect(0x43))
329 throw new IllegalArgumentException("data from remote end not a mode 3 response");
330 int count = stream.consume();
331 for (int i = 0; i < count; ++i) {
332 result.add(getDiagnosticTroubleCode(stream));
333 }
334 return result;
335 }
253} 336}
diff --git a/obd2-lib/src/com/android/car/obd2/Obd2FreezeFrameGenerator.java b/obd2-lib/src/com/android/car/obd2/Obd2FreezeFrameGenerator.java
new file mode 100644
index 00000000..3de48638
--- /dev/null
+++ b/obd2-lib/src/com/android/car/obd2/Obd2FreezeFrameGenerator.java
@@ -0,0 +1,177 @@
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.os.SystemClock;
20import android.util.JsonWriter;
21import android.util.Log;
22import com.android.car.obd2.Obd2Command.FreezeFrameCommand;
23import com.android.car.obd2.Obd2Command.OutputSemanticHandler;
24import java.io.IOException;
25import java.util.ArrayList;
26import java.util.List;
27import java.util.Optional;
28import java.util.Set;
29
30public class Obd2FreezeFrameGenerator {
31 public static final String FRAME_TYPE_FREEZE = "freeze";
32 public static final String TAG = Obd2FreezeFrameGenerator.class.getSimpleName();
33
34 private final Obd2Connection mConnection;
35 private final List<OutputSemanticHandler<Integer>> mIntegerCommands = new ArrayList<>();
36 private final List<OutputSemanticHandler<Float>> mFloatCommands = new ArrayList<>();
37
38 private List<String> mPreviousDtcs = new ArrayList<>();
39
40 public Obd2FreezeFrameGenerator(Obd2Connection connection)
41 throws IOException, InterruptedException {
42 mConnection = connection;
43 Set<Integer> connectionPids = connection.getSupportedPIDs();
44 Set<Integer> apiIntegerPids = Obd2Command.getSupportedIntegerCommands();
45 Set<Integer> apiFloatPids = Obd2Command.getSupportedFloatCommands();
46 apiIntegerPids
47 .stream()
48 .filter(connectionPids::contains)
49 .forEach((Integer pid) -> mIntegerCommands.add(Obd2Command.getIntegerCommand(pid)));
50 apiFloatPids
51 .stream()
52 .filter(connectionPids::contains)
53 .forEach((Integer pid) -> mFloatCommands.add(Obd2Command.getFloatCommand(pid)));
54 Log.i(
55 TAG,
56 String.format(
57 "connectionPids = %s\napiIntegerPids=%s\napiFloatPids = %s\n"
58 + "mIntegerCommands = %s\nmFloatCommands = %s\n",
59 connectionPids,
60 apiIntegerPids,
61 apiFloatPids,
62 mIntegerCommands,
63 mFloatCommands));
64 }
65
66 public JsonWriter generate(JsonWriter jsonWriter) throws IOException, InterruptedException {
67 return generate(jsonWriter, SystemClock.elapsedRealtimeNanos());
68 }
69
70 // OBD2 does not have a notion of timestamping the fault codes
71 // As such, we need to perform additional magic in order to figure out
72 // whether a fault code we retrieved is the same as a fault code we already
73 // saw in a past iteration. The logic goes as follows:
74 // for every position i in currentDtcs, if mPreviousDtcs[i] is the same
75 // fault code, then assume they are identical. If they are not the same fault code,
76 // then everything in currentDtcs[i...size()) is assumed to be a new fault code as
77 // something in the list must have moved around; if currentDtcs is shorter than
78 // mPreviousDtcs then obviously exit at the end of currentDtcs; if currentDtcs
79 // is longer, however, anything in currentDtcs past the end of mPreviousDtcs is a new
80 // fault code and will be included
81 private final class FreezeFrameIdentity {
82 public final String dtc;
83 public final int id;
84
85 FreezeFrameIdentity(String dtc, int id) {
86 this.dtc = dtc;
87 this.id = id;
88 }
89 }
90
91 private List<FreezeFrameIdentity> discoverNewDtcs(List<String> currentDtcs) {
92 List<FreezeFrameIdentity> newDtcs = new ArrayList<>();
93 int currentIndex = 0;
94 boolean inCopyAllMode = false;
95
96 for (; currentIndex < currentDtcs.size(); ++currentIndex) {
97 if (currentIndex == mPreviousDtcs.size()) {
98 // we have more current DTCs than previous DTCs, copy everything
99 inCopyAllMode = true;
100 break;
101 }
102 if (!currentDtcs.get(currentIndex).equals(mPreviousDtcs.get(currentIndex))) {
103 // we found a different DTC, copy everything
104 inCopyAllMode = true;
105 break;
106 }
107 // same DTC, not at end of either list yet, keep looping
108 }
109
110 if (inCopyAllMode) {
111 for (; currentIndex < currentDtcs.size(); ++currentIndex) {
112 newDtcs.add(new FreezeFrameIdentity(currentDtcs.get(currentIndex), currentIndex));
113 }
114 }
115
116 return newDtcs;
117 }
118
119 public JsonWriter generate(JsonWriter jsonWriter, long timestamp)
120 throws IOException, InterruptedException {
121 List<String> currentDtcs = mConnection.getDiagnosticTroubleCodes();
122 List<FreezeFrameIdentity> newDtcs = discoverNewDtcs(currentDtcs);
123 mPreviousDtcs = currentDtcs;
124 for (FreezeFrameIdentity freezeFrame : newDtcs) {
125 jsonWriter.beginObject();
126 jsonWriter.name("type").value(FRAME_TYPE_FREEZE);
127 jsonWriter.name("timestamp").value(timestamp);
128 jsonWriter.name("stringValue").value(freezeFrame.dtc);
129 jsonWriter.name("intValues").beginArray();
130 for (OutputSemanticHandler<Integer> handler : mIntegerCommands) {
131 FreezeFrameCommand<Integer> command =
132 Obd2Command.getFreezeFrameCommand(handler, freezeFrame.id);
133 try {
134 Optional<Integer> result = command.run(mConnection);
135 if (result.isPresent()) {
136 jsonWriter.beginObject();
137 jsonWriter.name("id").value(command.getPid());
138 jsonWriter.name("value").value(result.get());
139 jsonWriter.endObject();
140 }
141 } catch (IOException | InterruptedException e) {
142 Log.w(
143 TAG,
144 String.format(
145 "unable to retrieve OBD2 pid %d due to exception: %s",
146 command.getPid(), e));
147 // skip this entry
148 }
149 }
150 jsonWriter.endArray();
151 jsonWriter.name("floatValues").beginArray();
152 for (OutputSemanticHandler<Float> handler : mFloatCommands) {
153 FreezeFrameCommand<Float> command =
154 Obd2Command.getFreezeFrameCommand(handler, freezeFrame.id);
155 try {
156 Optional<Float> result = command.run(mConnection);
157 if (result.isPresent()) {
158 jsonWriter.beginObject();
159 jsonWriter.name("id").value(command.getPid());
160 jsonWriter.name("value").value(result.get());
161 jsonWriter.endObject();
162 }
163 } catch (IOException | InterruptedException e) {
164 Log.w(
165 TAG,
166 String.format(
167 "unable to retrieve OBD2 pid %d due to exception: %s",
168 command.getPid(), e));
169 // skip this entry
170 }
171 }
172 jsonWriter.endArray();
173 jsonWriter.endObject();
174 }
175 return jsonWriter;
176 }
177}
diff --git a/obd2-lib/src/com/android/car/obd2/commands/RPM.java b/obd2-lib/src/com/android/car/obd2/commands/RPM.java
index b277abfc..a062e978 100644
--- a/obd2-lib/src/com/android/car/obd2/commands/RPM.java
+++ b/obd2-lib/src/com/android/car/obd2/commands/RPM.java
@@ -30,7 +30,7 @@ public class RPM implements Obd2Command.OutputSemanticHandler<Integer> {
30 public Optional<Integer> consume(IntegerArrayStream data) { 30 public Optional<Integer> consume(IntegerArrayStream data) {
31 return data.hasAtLeast( 31 return data.hasAtLeast(
32 2, 32 2,
33 theData -> Optional.of(theData.consume() * 256 + theData.consume() / 4), 33 theData -> Optional.of((theData.consume() * 256 + theData.consume()) / 4),
34 theData -> Optional.<Integer>empty()); 34 theData -> Optional.<Integer>empty());
35 } 35 }
36} 36}