blob: 577b7989336b24b79f1fc71b282153b476c9fb5c [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
Enrico Granata7888bdc2017-03-22 14:21:52 -070039 boolean isConnected();
40
Enrico Granata759d2912017-03-14 12:33:30 -070041 InputStream getInputStream();
42
43 OutputStream getOutputStream();
44 }
45
46 private final UnderlyingTransport mConnection;
47
48 private static final String[] initCommands =
49 new String[] {"ATD", "ATZ", "AT E0", "AT L0", "AT S0", "AT H0", "AT SP 0"};
50
51 public Obd2Connection(UnderlyingTransport connection) {
52 mConnection = Objects.requireNonNull(connection);
53 runInitCommands();
54 }
55
56 public String getAddress() {
57 return mConnection.getAddress();
58 }
59
60 private void runInitCommands() {
61 for (final String initCommand : initCommands) {
62 try {
63 runImpl(initCommand);
64 } catch (IOException | InterruptedException e) {
65 }
66 }
67 }
68
69 public boolean reconnect() {
70 if (!mConnection.reconnect()) return false;
71 runInitCommands();
72 return true;
73 }
74
Enrico Granatafb08d622017-03-28 10:36:16 -070075 public boolean isConnected() {
76 return mConnection.isConnected();
77 }
78
Enrico Granata759d2912017-03-14 12:33:30 -070079 static int toDigitValue(char c) {
80 if ((c >= '0') && (c <= '9')) return c - '0';
81 switch (c) {
82 case 'a':
83 case 'A':
84 return 10;
85 case 'b':
86 case 'B':
87 return 11;
88 case 'c':
89 case 'C':
90 return 12;
91 case 'd':
92 case 'D':
93 return 13;
94 case 'e':
95 case 'E':
96 return 14;
97 case 'f':
98 case 'F':
99 return 15;
100 default:
101 throw new IllegalArgumentException(c + " is not a valid hex digit");
102 }
103 }
104
105 int[] toHexValues(String buffer) {
106 int[] values = new int[buffer.length() / 2];
107 for (int i = 0; i < values.length; ++i) {
108 values[i] =
109 16 * toDigitValue(buffer.charAt(2 * i))
110 + toDigitValue(buffer.charAt(2 * i + 1));
111 }
112 return values;
113 }
114
115 private String runImpl(String command) throws IOException, InterruptedException {
116 InputStream in = Objects.requireNonNull(mConnection.getInputStream());
117 OutputStream out = Objects.requireNonNull(mConnection.getOutputStream());
118
119 out.write((command + "\r").getBytes());
120 out.flush();
121
122 StringBuilder response = new StringBuilder();
123 while (true) {
124 int value = in.read();
125 if (value < 0) continue;
126 char c = (char) value;
127 // this is the prompt, stop here
128 if (c == '>') break;
129 if (c == '\r' || c == '\n' || c == ' ' || c == '\t' || c == '.') continue;
130 response.append(c);
131 }
132
133 String responseValue = response.toString();
134 return responseValue;
135 }
136
137 String removeSideData(String response, String... patterns) {
138 for (String pattern : patterns) {
139 if (response.contains(pattern)) response = response.replaceAll(pattern, "");
140 }
141 return response;
142 }
143
144 public int[] run(String command) throws IOException, InterruptedException {
145 String responseValue = runImpl(command);
146 String originalResponseValue = responseValue;
147 if (responseValue.startsWith(command))
148 responseValue = responseValue.substring(command.length());
149 //TODO(egranata): should probably handle these intelligently
150 responseValue =
151 removeSideData(
152 responseValue,
153 "SEARCHING",
154 "ERROR",
155 "BUS INIT",
156 "BUSINIT",
157 "BUS ERROR",
158 "BUSERROR",
159 "STOPPED");
160 if (responseValue.equals("OK")) return new int[] {1};
161 if (responseValue.equals("?")) return new int[] {0};
162 if (responseValue.equals("NODATA")) return new int[] {};
163 if (responseValue.equals("UNABLETOCONNECT")) throw new IOException("connection failure");
164 try {
165 return toHexValues(responseValue);
166 } catch (IllegalArgumentException e) {
167 Log.e(
168 "OBD2",
169 String.format(
170 "conversion error: command: '%s', original response: '%s'"
171 + ", processed response: '%s'",
172 command, originalResponseValue, responseValue));
173 throw e;
174 }
175 }
176
177 static class FourByteBitSet {
178 private static final int[] masks =
179 new int[] {
180 0b0000_0001,
181 0b0000_0010,
182 0b0000_0100,
183 0b0000_1000,
184 0b0001_0000,
185 0b0010_0000,
186 0b0100_0000,
187 0b1000_0000
188 };
189
190 private final byte mByte0;
191 private final byte mByte1;
192 private final byte mByte2;
193 private final byte mByte3;
194
195 FourByteBitSet(byte b0, byte b1, byte b2, byte b3) {
196 mByte0 = b0;
197 mByte1 = b1;
198 mByte2 = b2;
199 mByte3 = b3;
200 }
201
202 private byte getByte(int index) {
203 switch (index) {
204 case 0:
205 return mByte0;
206 case 1:
207 return mByte1;
208 case 2:
209 return mByte2;
210 case 3:
211 return mByte3;
212 default:
213 throw new IllegalArgumentException(index + " is not a valid byte index");
214 }
215 }
216
217 private boolean getBit(byte b, int index) {
218 if (index < 0 || index >= masks.length)
219 throw new IllegalArgumentException(index + " is not a valid bit index");
220 return 0 != (b & masks[index]);
221 }
222
223 public boolean getBit(int b, int index) {
224 return getBit(getByte(b), index);
225 }
226 }
227
228 public Set<Integer> getSupportedPIDs() throws IOException, InterruptedException {
229 Set<Integer> result = new HashSet<>();
230 String[] pids = new String[] {"0100", "0120", "0140", "0160"};
231 int basePid = 0;
232 for (String pid : pids) {
233 int[] responseData = run(pid);
234 if (responseData.length >= 6) {
235 byte byte0 = (byte) (responseData[2] & 0xFF);
236 byte byte1 = (byte) (responseData[3] & 0xFF);
237 byte byte2 = (byte) (responseData[4] & 0xFF);
238 byte byte3 = (byte) (responseData[5] & 0xFF);
239 FourByteBitSet fourByteBitSet = new FourByteBitSet(byte0, byte1, byte2, byte3);
240 for (int byteIndex = 0; byteIndex < 4; ++byteIndex) {
241 for (int bitIndex = 7; bitIndex >= 0; --bitIndex) {
242 if (fourByteBitSet.getBit(byteIndex, bitIndex)) {
243 result.add(basePid + 8 * byteIndex + 7 - bitIndex);
244 }
245 }
246 }
247 }
248 basePid += 0x20;
249 }
250
251 return result;
252 }
253}