upload android base code part8

This commit is contained in:
August 2018-08-08 20:10:12 +08:00
parent 841ae54672
commit 5425409085
57075 changed files with 9846578 additions and 0 deletions

View file

@ -0,0 +1,33 @@
# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
#disble build in PDK, missing aidl import breaks build
ifneq ($(TARGET_BUILD_PDK),true)
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_MODULE := com.android.car.obd2
LOCAL_JAVA_LANGUAGE_VERSION := 1.8
LOCAL_STATIC_JAVA_LIBRARIES += vehicle-hal-support-lib \
include $(BUILD_STATIC_JAVA_LIBRARY)
endif #TARGET_BUILD_PDK

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
package="com.android.car.obd2" >
<uses-sdk android:minSdkVersion="23" android:targetSdkVersion="23" />
</manifest>

View file

@ -0,0 +1,99 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.obd2;
import java.util.function.Function;
/**
* A wrapper over an int[] that offers a moving offset into the array, allowing for sequential
* consumption of the array data
*/
public class IntegerArrayStream {
private final int[] mData;
private int mIndex;
public IntegerArrayStream(int[] data) {
mData = data;
mIndex = 0;
}
public int peek() {
return mData[mIndex];
}
public int consume() {
return mData[mIndex++];
}
public int residualLength() {
return mData.length - mIndex;
}
public boolean isEmpty() {
return residualLength() == 0;
}
public boolean hasAtLeast(int n) {
return residualLength() >= n;
}
public <T> T hasAtLeast(int n, Function<IntegerArrayStream, T> ifTrue) {
return hasAtLeast(n, ifTrue, null);
}
public <T> T hasAtLeast(
int n,
Function<IntegerArrayStream, T> ifTrue,
Function<IntegerArrayStream, T> ifFalse) {
if (hasAtLeast(n)) {
return ifTrue.apply(this);
} else {
if (ifFalse != null) {
return ifFalse.apply(this);
} else {
return null;
}
}
}
/**
* Validates the content of this stream against an expected data-set.
*
* <p>If any element of values causes a mismatch, that element will not be consumed and this
* method will return false. All elements that do match are consumed from the stream.
*
* <p>For instance, given a stream with {1,2,3,4}, a call of expect(1,2,5) will consume 1 and 2,
* will return false, and stream.peek() will return 3 since it is the first element that did not
* match and was not consumed.
*
* @param values The values to compare this stream's elements against.
* @return true if all elements of values match this stream, false otherwise.
*/
public boolean expect(int... values) {
if (!hasAtLeast(values.length)) {
return false;
}
for (int value : values) {
if (value != peek()) {
return false;
} else {
consume();
}
}
return true;
}
}

View file

@ -0,0 +1,196 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.obd2;
import com.android.car.obd2.commands.AmbientAirTemperature;
import com.android.car.obd2.commands.CalculatedEngineLoad;
import com.android.car.obd2.commands.EngineCoolantTemperature;
import com.android.car.obd2.commands.EngineOilTemperature;
import com.android.car.obd2.commands.EngineRuntime;
import com.android.car.obd2.commands.FuelGaugePressure;
import com.android.car.obd2.commands.FuelSystemStatus;
import com.android.car.obd2.commands.FuelTankLevel;
import com.android.car.obd2.commands.FuelTrimCommand.Bank1LongTermFuelTrimCommand;
import com.android.car.obd2.commands.FuelTrimCommand.Bank1ShortTermFuelTrimCommand;
import com.android.car.obd2.commands.FuelTrimCommand.Bank2LongTermFuelTrimCommand;
import com.android.car.obd2.commands.FuelTrimCommand.Bank2ShortTermFuelTrimCommand;
import com.android.car.obd2.commands.RPM;
import com.android.car.obd2.commands.Speed;
import com.android.car.obd2.commands.ThrottlePosition;
import java.io.IOException;
import java.util.HashMap;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
/**
* Base class of OBD2 command objects that query a "vehicle" and return an individual data point
* represented as a Java type.
*
* @param <ValueType> The Java type that represents the value of this command's output.
*/
public abstract class Obd2Command<ValueType> {
/**
* Abstract representation of an object whose job it is to receive the bytes read from the OBD2
* connection and return a Java representation of a command's value.
*
* @param <ValueType>
*/
public interface OutputSemanticHandler<ValueType> {
int getPid();
Optional<ValueType> consume(IntegerArrayStream data);
}
public static final int LIVE_FRAME = 1;
public static final int FREEZE_FRAME = 2;
private static final HashMap<Integer, OutputSemanticHandler<Integer>>
SUPPORTED_INTEGER_COMMANDS = new HashMap<>();
private static final HashMap<Integer, OutputSemanticHandler<Float>> SUPPORTED_FLOAT_COMMANDS =
new HashMap<>();
private static void addSupportedIntegerCommands(
OutputSemanticHandler<Integer>... integerOutputSemanticHandlers) {
for (OutputSemanticHandler<Integer> integerOutputSemanticHandler :
integerOutputSemanticHandlers) {
SUPPORTED_INTEGER_COMMANDS.put(
integerOutputSemanticHandler.getPid(), integerOutputSemanticHandler);
}
}
private static void addSupportedFloatCommands(
OutputSemanticHandler<Float>... floatOutputSemanticHandlers) {
for (OutputSemanticHandler<Float> floatOutputSemanticHandler :
floatOutputSemanticHandlers) {
SUPPORTED_FLOAT_COMMANDS.put(
floatOutputSemanticHandler.getPid(), floatOutputSemanticHandler);
}
}
public static Set<Integer> getSupportedIntegerCommands() {
return SUPPORTED_INTEGER_COMMANDS.keySet();
}
public static Set<Integer> getSupportedFloatCommands() {
return SUPPORTED_FLOAT_COMMANDS.keySet();
}
public static OutputSemanticHandler<Integer> getIntegerCommand(int pid) {
return SUPPORTED_INTEGER_COMMANDS.get(pid);
}
public static OutputSemanticHandler<Float> getFloatCommand(int pid) {
return SUPPORTED_FLOAT_COMMANDS.get(pid);
}
static {
addSupportedFloatCommands(
new AmbientAirTemperature(),
new CalculatedEngineLoad(),
new FuelTankLevel(),
new Bank2ShortTermFuelTrimCommand(),
new Bank2LongTermFuelTrimCommand(),
new Bank1LongTermFuelTrimCommand(),
new Bank1ShortTermFuelTrimCommand(),
new ThrottlePosition());
addSupportedIntegerCommands(
new EngineOilTemperature(),
new EngineCoolantTemperature(),
new FuelGaugePressure(),
new FuelSystemStatus(),
new RPM(),
new EngineRuntime(),
new Speed());
}
protected final int mMode;
protected final OutputSemanticHandler<ValueType> mSemanticHandler;
Obd2Command(int mode, OutputSemanticHandler<ValueType> semanticHandler) {
mMode = mode;
mSemanticHandler = Objects.requireNonNull(semanticHandler);
}
public abstract Optional<ValueType> run(Obd2Connection connection) throws Exception;
public int getPid() {
return mSemanticHandler.getPid();
}
public static final <T> LiveFrameCommand<T> getLiveFrameCommand(OutputSemanticHandler handler) {
return new LiveFrameCommand<>(handler);
}
public static final <T> FreezeFrameCommand<T> getFreezeFrameCommand(
OutputSemanticHandler handler, int frameId) {
return new FreezeFrameCommand<>(handler, frameId);
}
/**
* An OBD2 command that returns live frame data.
*
* @param <ValueType> The Java type that represents the command's result type.
*/
public static class LiveFrameCommand<ValueType> extends Obd2Command<ValueType> {
private static final int RESPONSE_MARKER = 0x41;
LiveFrameCommand(OutputSemanticHandler<ValueType> semanticHandler) {
super(LIVE_FRAME, semanticHandler);
}
public Optional<ValueType> run(Obd2Connection connection)
throws IOException, InterruptedException {
String command = String.format("%02X%02X", mMode, mSemanticHandler.getPid());
int[] data = connection.run(command);
IntegerArrayStream stream = new IntegerArrayStream(data);
if (stream.expect(RESPONSE_MARKER, mSemanticHandler.getPid())) {
return mSemanticHandler.consume(stream);
}
return Optional.empty();
}
}
/**
* An OBD2 command that returns freeze frame data.
*
* @param <ValueType> The Java type that represents the command's result type.
*/
public static class FreezeFrameCommand<ValueType> extends Obd2Command<ValueType> {
private static final int RESPONSE_MARKER = 0x42;
private int mFrameId;
FreezeFrameCommand(OutputSemanticHandler<ValueType> semanticHandler, int frameId) {
super(FREEZE_FRAME, semanticHandler);
mFrameId = frameId;
}
public Optional<ValueType> run(Obd2Connection connection)
throws IOException, InterruptedException {
String command =
String.format("%02X%02X %02X", mMode, mSemanticHandler.getPid(), mFrameId);
int[] data = connection.run(command);
IntegerArrayStream stream = new IntegerArrayStream(data);
if (stream.expect(RESPONSE_MARKER, mSemanticHandler.getPid(), mFrameId)) {
return mSemanticHandler.consume(stream);
}
return Optional.empty();
}
}
}

View file

@ -0,0 +1,336 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.obd2;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/** This class represents a connection between Java code and a "vehicle" that talks OBD2. */
public class Obd2Connection {
private static final String TAG = Obd2Connection.class.getSimpleName();
private static final boolean DBG = false;
/**
* The transport layer that moves OBD2 requests from us to the remote entity and viceversa. It
* is possible for this to be USB, Bluetooth, or just as simple as a pty for a simulator.
*/
public interface UnderlyingTransport {
String getAddress();
boolean reconnect();
boolean isConnected();
InputStream getInputStream();
OutputStream getOutputStream();
}
private final UnderlyingTransport mConnection;
private static final String[] initCommands =
new String[] {"ATD", "ATZ", "AT E0", "AT L0", "AT S0", "AT H0", "AT SP 0"};
public Obd2Connection(UnderlyingTransport connection) {
mConnection = Objects.requireNonNull(connection);
runInitCommands();
}
public String getAddress() {
return mConnection.getAddress();
}
private void runInitCommands() {
for (final String initCommand : initCommands) {
try {
runImpl(initCommand);
} catch (IOException | InterruptedException e) {
}
}
}
public boolean reconnect() {
if (!mConnection.reconnect()) return false;
runInitCommands();
return true;
}
public boolean isConnected() {
return mConnection.isConnected();
}
static int toDigitValue(char c) {
if ((c >= '0') && (c <= '9')) return c - '0';
switch (c) {
case 'a':
case 'A':
return 10;
case 'b':
case 'B':
return 11;
case 'c':
case 'C':
return 12;
case 'd':
case 'D':
return 13;
case 'e':
case 'E':
return 14;
case 'f':
case 'F':
return 15;
default:
throw new IllegalArgumentException(c + " is not a valid hex digit");
}
}
int[] toHexValues(String buffer) {
int[] values = new int[buffer.length() / 2];
for (int i = 0; i < values.length; ++i) {
values[i] =
16 * toDigitValue(buffer.charAt(2 * i))
+ toDigitValue(buffer.charAt(2 * i + 1));
}
return values;
}
private String runImpl(String command) throws IOException, InterruptedException {
InputStream in = Objects.requireNonNull(mConnection.getInputStream());
OutputStream out = Objects.requireNonNull(mConnection.getOutputStream());
if (DBG) {
Log.i(TAG, "runImpl(" + command + ")");
}
out.write((command + "\r").getBytes());
out.flush();
StringBuilder response = new StringBuilder();
while (true) {
int value = in.read();
if (value < 0) continue;
char c = (char) value;
// this is the prompt, stop here
if (c == '>') break;
if (c == '\r' || c == '\n' || c == ' ' || c == '\t' || c == '.') continue;
response.append(c);
}
String responseValue = response.toString();
if (DBG) {
Log.i(TAG, "runImpl() returned " + responseValue);
}
return responseValue;
}
String removeSideData(String response, String... patterns) {
for (String pattern : patterns) {
if (response.contains(pattern)) response = response.replaceAll(pattern, "");
}
return response;
}
String unpackLongFrame(String response) {
// long frames come back to us containing colon separated portions
if (response.indexOf(':') < 0) return response;
// remove everything until the first colon
response = response.substring(response.indexOf(':') + 1);
// then remove the <digit>: portions (sequential frame parts)
//TODO(egranata): maybe validate the sequence of digits is progressive
return response.replaceAll("[0-9]:", "");
}
public int[] run(String command) throws IOException, InterruptedException {
String responseValue = runImpl(command);
String originalResponseValue = responseValue;
String unspacedCommand = command.replaceAll(" ", "");
if (responseValue.startsWith(unspacedCommand))
responseValue = responseValue.substring(unspacedCommand.length());
responseValue = unpackLongFrame(responseValue);
if (DBG) {
Log.i(TAG, "post-processed response " + responseValue);
}
//TODO(egranata): should probably handle these intelligently
responseValue =
removeSideData(
responseValue,
"SEARCHING",
"ERROR",
"BUS INIT",
"BUSINIT",
"BUS ERROR",
"BUSERROR",
"STOPPED");
if (responseValue.equals("OK")) return new int[] {1};
if (responseValue.equals("?")) return new int[] {0};
if (responseValue.equals("NODATA")) return new int[] {};
if (responseValue.equals("UNABLETOCONNECT")) throw new IOException("connection failure");
if (responseValue.equals("CANERROR")) throw new IOException("CAN bus error");
try {
return toHexValues(responseValue);
} catch (IllegalArgumentException e) {
Log.e(
TAG,
String.format(
"conversion error: command: '%s', original response: '%s'"
+ ", processed response: '%s'",
command, originalResponseValue, responseValue));
throw e;
}
}
static class FourByteBitSet {
private static final int[] masks =
new int[] {
0b0000_0001,
0b0000_0010,
0b0000_0100,
0b0000_1000,
0b0001_0000,
0b0010_0000,
0b0100_0000,
0b1000_0000
};
private final byte mByte0;
private final byte mByte1;
private final byte mByte2;
private final byte mByte3;
FourByteBitSet(byte b0, byte b1, byte b2, byte b3) {
mByte0 = b0;
mByte1 = b1;
mByte2 = b2;
mByte3 = b3;
}
private byte getByte(int index) {
switch (index) {
case 0:
return mByte0;
case 1:
return mByte1;
case 2:
return mByte2;
case 3:
return mByte3;
default:
throw new IllegalArgumentException(index + " is not a valid byte index");
}
}
private boolean getBit(byte b, int index) {
if (index < 0 || index >= masks.length)
throw new IllegalArgumentException(index + " is not a valid bit index");
return 0 != (b & masks[index]);
}
public boolean getBit(int b, int index) {
return getBit(getByte(b), index);
}
}
public Set<Integer> getSupportedPIDs() throws IOException, InterruptedException {
Set<Integer> result = new HashSet<>();
String[] pids = new String[] {"0100", "0120", "0140", "0160"};
int basePid = 1;
for (String pid : pids) {
int[] responseData = run(pid);
if (responseData.length >= 6) {
byte byte0 = (byte) (responseData[2] & 0xFF);
byte byte1 = (byte) (responseData[3] & 0xFF);
byte byte2 = (byte) (responseData[4] & 0xFF);
byte byte3 = (byte) (responseData[5] & 0xFF);
if (DBG) {
Log.i(TAG, String.format("supported PID at base %d payload %02X%02X%02X%02X",
basePid, byte0, byte1, byte2, byte3));
}
FourByteBitSet fourByteBitSet = new FourByteBitSet(byte0, byte1, byte2, byte3);
for (int byteIndex = 0; byteIndex < 4; ++byteIndex) {
for (int bitIndex = 7; bitIndex >= 0; --bitIndex) {
if (fourByteBitSet.getBit(byteIndex, bitIndex)) {
int command = basePid + 8 * byteIndex + 7 - bitIndex;
if (DBG) {
Log.i(TAG, "command " + command + " found supported");
}
result.add(command);
}
}
}
}
basePid += 0x20;
}
return result;
}
String getDiagnosticTroubleCode(IntegerArrayStream source) {
final char[] components = new char[] {'P', 'C', 'B', 'U'};
final char[] firstDigits = new char[] {'0', '1', '2', '3'};
final char[] otherDigits =
new char[] {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
StringBuilder builder = new StringBuilder(5);
int byte0 = source.consume();
int byte1 = source.consume();
int componentMask = (byte0 & 0xC0) >> 6;
int firstDigitMask = (byte0 & 0x30) >> 4;
int secondDigitMask = (byte0 & 0x0F);
int thirdDigitMask = (byte1 & 0xF0) >> 4;
int fourthDigitMask = (byte1 & 0x0F);
builder.append(components[componentMask]);
builder.append(firstDigits[firstDigitMask]);
builder.append(otherDigits[secondDigitMask]);
builder.append(otherDigits[thirdDigitMask]);
builder.append(otherDigits[fourthDigitMask]);
return builder.toString();
}
public List<String> getDiagnosticTroubleCodes() throws IOException, InterruptedException {
List<String> result = new ArrayList<>();
int[] response = run("03");
IntegerArrayStream stream = new IntegerArrayStream(response);
if (stream.isEmpty()) return result;
if (!stream.expect(0x43))
throw new IllegalArgumentException("data from remote end not a mode 3 response");
int count = stream.consume();
for (int i = 0; i < count; ++i) {
result.add(getDiagnosticTroubleCode(stream));
}
return result;
}
}

View file

@ -0,0 +1,177 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.obd2;
import android.os.SystemClock;
import android.util.JsonWriter;
import android.util.Log;
import com.android.car.obd2.Obd2Command.FreezeFrameCommand;
import com.android.car.obd2.Obd2Command.OutputSemanticHandler;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
public class Obd2FreezeFrameGenerator {
public static final String FRAME_TYPE_FREEZE = "freeze";
public static final String TAG = Obd2FreezeFrameGenerator.class.getSimpleName();
private final Obd2Connection mConnection;
private final List<OutputSemanticHandler<Integer>> mIntegerCommands = new ArrayList<>();
private final List<OutputSemanticHandler<Float>> mFloatCommands = new ArrayList<>();
private List<String> mPreviousDtcs = new ArrayList<>();
public Obd2FreezeFrameGenerator(Obd2Connection connection)
throws IOException, InterruptedException {
mConnection = connection;
Set<Integer> connectionPids = connection.getSupportedPIDs();
Set<Integer> apiIntegerPids = Obd2Command.getSupportedIntegerCommands();
Set<Integer> apiFloatPids = Obd2Command.getSupportedFloatCommands();
apiIntegerPids
.stream()
.filter(connectionPids::contains)
.forEach((Integer pid) -> mIntegerCommands.add(Obd2Command.getIntegerCommand(pid)));
apiFloatPids
.stream()
.filter(connectionPids::contains)
.forEach((Integer pid) -> mFloatCommands.add(Obd2Command.getFloatCommand(pid)));
Log.i(
TAG,
String.format(
"connectionPids = %s\napiIntegerPids=%s\napiFloatPids = %s\n"
+ "mIntegerCommands = %s\nmFloatCommands = %s\n",
connectionPids,
apiIntegerPids,
apiFloatPids,
mIntegerCommands,
mFloatCommands));
}
public JsonWriter generate(JsonWriter jsonWriter) throws IOException, InterruptedException {
return generate(jsonWriter, SystemClock.elapsedRealtimeNanos());
}
// OBD2 does not have a notion of timestamping the fault codes
// As such, we need to perform additional magic in order to figure out
// whether a fault code we retrieved is the same as a fault code we already
// saw in a past iteration. The logic goes as follows:
// for every position i in currentDtcs, if mPreviousDtcs[i] is the same
// fault code, then assume they are identical. If they are not the same fault code,
// then everything in currentDtcs[i...size()) is assumed to be a new fault code as
// something in the list must have moved around; if currentDtcs is shorter than
// mPreviousDtcs then obviously exit at the end of currentDtcs; if currentDtcs
// is longer, however, anything in currentDtcs past the end of mPreviousDtcs is a new
// fault code and will be included
private final class FreezeFrameIdentity {
public final String dtc;
public final int id;
FreezeFrameIdentity(String dtc, int id) {
this.dtc = dtc;
this.id = id;
}
}
private List<FreezeFrameIdentity> discoverNewDtcs(List<String> currentDtcs) {
List<FreezeFrameIdentity> newDtcs = new ArrayList<>();
int currentIndex = 0;
boolean inCopyAllMode = false;
for (; currentIndex < currentDtcs.size(); ++currentIndex) {
if (currentIndex == mPreviousDtcs.size()) {
// we have more current DTCs than previous DTCs, copy everything
inCopyAllMode = true;
break;
}
if (!currentDtcs.get(currentIndex).equals(mPreviousDtcs.get(currentIndex))) {
// we found a different DTC, copy everything
inCopyAllMode = true;
break;
}
// same DTC, not at end of either list yet, keep looping
}
if (inCopyAllMode) {
for (; currentIndex < currentDtcs.size(); ++currentIndex) {
newDtcs.add(new FreezeFrameIdentity(currentDtcs.get(currentIndex), currentIndex));
}
}
return newDtcs;
}
public JsonWriter generate(JsonWriter jsonWriter, long timestamp)
throws IOException, InterruptedException {
List<String> currentDtcs = mConnection.getDiagnosticTroubleCodes();
List<FreezeFrameIdentity> newDtcs = discoverNewDtcs(currentDtcs);
mPreviousDtcs = currentDtcs;
for (FreezeFrameIdentity freezeFrame : newDtcs) {
jsonWriter.beginObject();
jsonWriter.name("type").value(FRAME_TYPE_FREEZE);
jsonWriter.name("timestamp").value(timestamp);
jsonWriter.name("stringValue").value(freezeFrame.dtc);
jsonWriter.name("intValues").beginArray();
for (OutputSemanticHandler<Integer> handler : mIntegerCommands) {
FreezeFrameCommand<Integer> command =
Obd2Command.getFreezeFrameCommand(handler, freezeFrame.id);
try {
Optional<Integer> result = command.run(mConnection);
if (result.isPresent()) {
jsonWriter.beginObject();
jsonWriter.name("id").value(command.getPid());
jsonWriter.name("value").value(result.get());
jsonWriter.endObject();
}
} catch (IOException | InterruptedException e) {
Log.w(
TAG,
String.format(
"unable to retrieve OBD2 pid %d due to exception: %s",
command.getPid(), e));
// skip this entry
}
}
jsonWriter.endArray();
jsonWriter.name("floatValues").beginArray();
for (OutputSemanticHandler<Float> handler : mFloatCommands) {
FreezeFrameCommand<Float> command =
Obd2Command.getFreezeFrameCommand(handler, freezeFrame.id);
try {
Optional<Float> result = command.run(mConnection);
if (result.isPresent()) {
jsonWriter.beginObject();
jsonWriter.name("id").value(command.getPid());
jsonWriter.name("value").value(result.get());
jsonWriter.endObject();
}
} catch (IOException | InterruptedException e) {
Log.w(
TAG,
String.format(
"unable to retrieve OBD2 pid %d due to exception: %s",
command.getPid(), e));
// skip this entry
}
}
jsonWriter.endArray();
jsonWriter.endObject();
}
return jsonWriter;
}
}

View file

@ -0,0 +1,123 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.obd2;
import android.os.SystemClock;
import android.util.JsonWriter;
import android.util.Log;
import com.android.car.obd2.Obd2Command.LiveFrameCommand;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
public class Obd2LiveFrameGenerator {
public static final String FRAME_TYPE_LIVE = "live";
public static final String TAG = Obd2LiveFrameGenerator.class.getSimpleName();
private final Obd2Connection mConnection;
private final List<LiveFrameCommand<Integer>> mIntegerCommands = new ArrayList<>();
private final List<LiveFrameCommand<Float>> mFloatCommands = new ArrayList<>();
public Obd2LiveFrameGenerator(Obd2Connection connection)
throws IOException, InterruptedException {
mConnection = connection;
Set<Integer> connectionPids = connection.getSupportedPIDs();
Set<Integer> apiIntegerPids = Obd2Command.getSupportedIntegerCommands();
Set<Integer> apiFloatPids = Obd2Command.getSupportedFloatCommands();
apiIntegerPids
.stream()
.filter(connectionPids::contains)
.forEach(
(Integer pid) ->
mIntegerCommands.add(
Obd2Command.getLiveFrameCommand(
Obd2Command.getIntegerCommand(pid))));
apiFloatPids
.stream()
.filter(connectionPids::contains)
.forEach(
(Integer pid) ->
mFloatCommands.add(
Obd2Command.getLiveFrameCommand(
Obd2Command.getFloatCommand(pid))));
Log.i(
TAG,
String.format(
"connectionPids = %s\napiIntegerPids=%s\napiFloatPids = %s\n"
+ "mIntegerCommands = %s\nmFloatCommands = %s\n",
connectionPids,
apiIntegerPids,
apiFloatPids,
mIntegerCommands,
mFloatCommands));
}
public JsonWriter generate(JsonWriter jsonWriter) throws IOException {
return generate(jsonWriter, SystemClock.elapsedRealtimeNanos());
}
public JsonWriter generate(JsonWriter jsonWriter, long timestamp) throws IOException {
jsonWriter.beginObject();
jsonWriter.name("type").value(FRAME_TYPE_LIVE);
jsonWriter.name("timestamp").value(timestamp);
jsonWriter.name("intValues").beginArray();
for (LiveFrameCommand<Integer> command : mIntegerCommands) {
try {
Optional<Integer> result = command.run(mConnection);
if (result.isPresent()) {
jsonWriter.beginObject();
jsonWriter.name("id").value(command.getPid());
jsonWriter.name("value").value(result.get());
jsonWriter.endObject();
}
} catch (IOException | InterruptedException e) {
Log.w(
TAG,
String.format(
"unable to retrieve OBD2 pid %d due to exception: %s",
command.getPid(), e));
// skip this entry
}
}
jsonWriter.endArray();
jsonWriter.name("floatValues").beginArray();
for (LiveFrameCommand<Float> command : mFloatCommands) {
try {
Optional<Float> result = command.run(mConnection);
if (result.isPresent()) {
jsonWriter.beginObject();
jsonWriter.name("id").value(command.getPid());
jsonWriter.name("value").value(result.get());
jsonWriter.endObject();
}
} catch (IOException | InterruptedException e) {
Log.w(
TAG,
String.format(
"unable to retrieve OBD2 pid %d due to exception: %s",
command.getPid(), e));
// skip this entry
}
}
jsonWriter.endArray();
return jsonWriter.endObject();
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.obd2.commands;
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
import java.util.Optional;
public class AmbientAirTemperature implements Obd2Command.OutputSemanticHandler<Float> {
@Override
public int getPid() {
return 0x46;
}
@Override
public Optional<Float> consume(IntegerArrayStream data) {
return data.hasAtLeast(
1,
theData -> Optional.of(theData.consume() * (100.0f / 255.0f)),
theData -> Optional.<Float>empty());
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.obd2.commands;
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
import java.util.Optional;
public class CalculatedEngineLoad implements Obd2Command.OutputSemanticHandler<Float> {
@Override
public int getPid() {
return 0x04;
}
@Override
public Optional<Float> consume(IntegerArrayStream data) {
return data.hasAtLeast(
1,
theData -> Optional.of(theData.consume() / 2.25f),
theData -> Optional.<Float>empty());
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.obd2.commands;
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
import java.util.Optional;
public class EngineCoolantTemperature implements Obd2Command.OutputSemanticHandler<Integer> {
@Override
public int getPid() {
return 0x05;
}
@Override
public Optional<Integer> consume(IntegerArrayStream data) {
return data.hasAtLeast(
1,
theData -> Optional.of(theData.consume() - 40),
theData -> Optional.<Integer>empty());
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.obd2.commands;
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
import java.util.Optional;
public class EngineOilTemperature implements Obd2Command.OutputSemanticHandler<Integer> {
@Override
public int getPid() {
return 0x5C;
}
@Override
public Optional<Integer> consume(IntegerArrayStream data) {
return data.hasAtLeast(
1,
theData -> Optional.of(theData.consume() - 40),
theData -> Optional.<Integer>empty());
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.obd2.commands;
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
import java.util.Optional;
public class EngineRuntime implements Obd2Command.OutputSemanticHandler<Integer> {
@Override
public int getPid() {
return 0x1F;
}
@Override
public Optional<Integer> consume(IntegerArrayStream data) {
return data.hasAtLeast(
2,
theData -> Optional.of(theData.consume() * 256 + theData.consume()),
theData -> Optional.<Integer>empty());
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.obd2.commands;
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
import java.util.Optional;
public class FuelGaugePressure implements Obd2Command.OutputSemanticHandler<Integer> {
@Override
public int getPid() {
return 0x0A;
}
@Override
public Optional<Integer> consume(IntegerArrayStream data) {
return data.hasAtLeast(
1,
theData -> Optional.of(theData.consume() * 3),
theData -> Optional.<Integer>empty());
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.obd2.commands;
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
import java.util.Optional;
public class FuelSystemStatus implements Obd2Command.OutputSemanticHandler<Integer> {
@Override
public int getPid() {
return 0x03;
}
@Override
public Optional<Integer> consume(IntegerArrayStream data) {
return data.hasAtLeast(
1,
theData -> Optional.of(theData.consume()),
theData -> Optional.<Integer>empty());
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.obd2.commands;
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
import java.util.Optional;
public class FuelTankLevel implements Obd2Command.OutputSemanticHandler<Float> {
@Override
public int getPid() {
return 0x2F;
}
@Override
public Optional<Float> consume(IntegerArrayStream data) {
return data.hasAtLeast(
1,
theData -> Optional.of(theData.consume() * (100.0f / 255.0f)),
theData -> Optional.<Float>empty());
}
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.obd2.commands;
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
import java.util.Optional;
public abstract class FuelTrimCommand implements Obd2Command.OutputSemanticHandler<Float> {
@Override
public Optional<Float> consume(IntegerArrayStream data) {
return data.hasAtLeast(
1,
theData -> Optional.of(3 * theData.consume() / 1.28f - 100),
theData -> Optional.<Float>empty());
}
public static class Bank1ShortTermFuelTrimCommand extends FuelTrimCommand {
@Override
public int getPid() {
return 0x06;
}
}
public static class Bank1LongTermFuelTrimCommand extends FuelTrimCommand {
@Override
public int getPid() {
return 0x07;
}
}
public static class Bank2ShortTermFuelTrimCommand extends FuelTrimCommand {
@Override
public int getPid() {
return 0x08;
}
}
public static class Bank2LongTermFuelTrimCommand extends FuelTrimCommand {
@Override
public int getPid() {
return 0x09;
}
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.obd2.commands;
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
import java.util.Optional;
public class RPM implements Obd2Command.OutputSemanticHandler<Integer> {
@Override
public int getPid() {
return 0x0C;
}
@Override
public Optional<Integer> consume(IntegerArrayStream data) {
return data.hasAtLeast(
2,
theData -> Optional.of((theData.consume() * 256 + theData.consume()) / 4),
theData -> Optional.<Integer>empty());
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.obd2.commands;
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
import java.util.Optional;
public class Speed implements Obd2Command.OutputSemanticHandler<Integer> {
@Override
public int getPid() {
return 0x0D;
}
@Override
public Optional<Integer> consume(IntegerArrayStream data) {
return data.hasAtLeast(
1,
theData -> Optional.of(theData.consume()),
theData -> Optional.<Integer>empty());
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.obd2.commands;
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
import java.util.Optional;
public class ThrottlePosition implements Obd2Command.OutputSemanticHandler<Float> {
@Override
public int getPid() {
return 0x11;
}
@Override
public Optional<Float> consume(IntegerArrayStream data) {
return data.hasAtLeast(
1,
theData -> Optional.of(theData.consume() * 0.392157f),
theData -> Optional.<Float>empty());
}
}

View file

@ -0,0 +1,125 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.obd2.connections;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.util.Log;
import com.android.car.obd2.Obd2Connection;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Objects;
import java.util.UUID;
public class BluetoothConnection implements Obd2Connection.UnderlyingTransport {
/**
* This is the well-known UUID for the Bluetooth SPP (Serial Port Profile)
*/
private static final UUID SERIAL_PORT_PROFILE = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
private final BluetoothDevice mDevice;
private BluetoothSocket mSocket = null;
public static final String TAG = BluetoothConnection.class.getSimpleName();
public BluetoothConnection(String bluetoothAddress) {
this(BluetoothAdapter.getDefaultAdapter().getRemoteDevice(bluetoothAddress));
}
public BluetoothConnection(BluetoothDevice device) {
mDevice = Objects.requireNonNull(device);
connect();
}
@Override
public String getAddress() {
return mDevice.getAddress();
}
/**
* Establish an RFCOMM connection to the remote device.
*
* Assumes there is no existing connection.
*
* This method may take time to return (or even not return in pathological cases).
* It is a good idea to wrap it in some kind of Promise-like object.
*
* @return true if it could connect, false otherwise
*/
private boolean connect() {
try {
mSocket = mDevice.createRfcommSocketToServiceRecord(SERIAL_PORT_PROFILE);
mSocket.connect();
} catch (IOException e) {
Log.w(TAG, "BluetoothConnection couldn't be established due to an exception: " + e);
mSocket = null;
return false;
}
return mSocket.isConnected();
}
@Override
public boolean isConnected() {
return mSocket != null && mSocket.isConnected();
}
private void close() {
if (isConnected()) {
try {
mSocket.close();
} catch (IOException e) {
// we are letting go of the connection anyway, so log and continue
Log.w(TAG, "IOException during BluetoothSocket close(): " + e);
} finally {
mSocket = null;
}
}
}
@Override
public boolean reconnect() {
close();
return connect();
}
@Override
public InputStream getInputStream() {
if (isConnected()) {
try {
return mSocket.getInputStream();
} catch (IOException e) {
Log.w(TAG, "failed to get Bluetooth input stream: " + e);
}
}
return null;
}
@Override
public OutputStream getOutputStream() {
if (isConnected()) {
try {
return mSocket.getOutputStream();
} catch (IOException e) {
Log.w(TAG, "failed to get Bluetooth output stream: " + e);
}
}
return null;
}
}