blob: 62b070a091a7516b1fa0fa92b2dc3c1a6ca42f66 [file] [log] [blame]
Enrico Granata759d2912017-03-14 12:33:30 -07001/*
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}