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