/* * 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> mIntegerCommands = new ArrayList<>(); private final List> mFloatCommands = new ArrayList<>(); private List mPreviousDtcs = new ArrayList<>(); public Obd2FreezeFrameGenerator(Obd2Connection connection) throws IOException, InterruptedException { mConnection = connection; Set connectionPids = connection.getSupportedPIDs(); Set apiIntegerPids = Obd2Command.getSupportedIntegerCommands(); Set 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 discoverNewDtcs(List currentDtcs) { List 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 currentDtcs = mConnection.getDiagnosticTroubleCodes(); List 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 handler : mIntegerCommands) { FreezeFrameCommand command = Obd2Command.getFreezeFrameCommand(handler, freezeFrame.id); try { Optional 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 handler : mFloatCommands) { FreezeFrameCommand command = Obd2Command.getFreezeFrameCommand(handler, freezeFrame.id); try { Optional 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; } }