Home | History | Annotate | Download | only in obd2
      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         boolean isConnected();
     40 
     41         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 
     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 
    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 
    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 
    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 
    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 
    191         FourByteBitSet(byte b0, byte b1, byte b2, byte b3) {
    192             mByte0 = b0;
    193             mByte1 = b1;
    194             mByte2 = b2;
    195             mByte3 = b3;
    196         }
    197 
    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 
    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 
    219         public boolean getBit(int b, int index) {
    220             return getBit(getByte(b), index);
    221         }
    222     }
    223 
    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