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.os.SystemClock; 20 import android.util.JsonWriter; 21 import android.util.Log; 22 import com.android.car.obd2.Obd2Command.FreezeFrameCommand; 23 import com.android.car.obd2.Obd2Command.OutputSemanticHandler; 24 import java.io.IOException; 25 import java.util.ArrayList; 26 import java.util.List; 27 import java.util.Optional; 28 import java.util.Set; 29 30 public class Obd2FreezeFrameGenerator { 31 public static final String FRAME_TYPE_FREEZE = "freeze"; 32 public static final String TAG = Obd2FreezeFrameGenerator.class.getSimpleName(); 33 34 private final Obd2Connection mConnection; 35 private final List<OutputSemanticHandler<Integer>> mIntegerCommands = new ArrayList<>(); 36 private final List<OutputSemanticHandler<Float>> mFloatCommands = new ArrayList<>(); 37 38 private List<String> mPreviousDtcs = new ArrayList<>(); 39 Obd2FreezeFrameGenerator(Obd2Connection connection)40 public Obd2FreezeFrameGenerator(Obd2Connection connection) 41 throws IOException, InterruptedException { 42 mConnection = connection; 43 Set<Integer> connectionPids = connection.getSupportedPIDs(); 44 Set<Integer> apiIntegerPids = Obd2Command.getSupportedIntegerCommands(); 45 Set<Integer> apiFloatPids = Obd2Command.getSupportedFloatCommands(); 46 apiIntegerPids 47 .stream() 48 .filter(connectionPids::contains) 49 .forEach((Integer pid) -> mIntegerCommands.add(Obd2Command.getIntegerCommand(pid))); 50 apiFloatPids 51 .stream() 52 .filter(connectionPids::contains) 53 .forEach((Integer pid) -> mFloatCommands.add(Obd2Command.getFloatCommand(pid))); 54 Log.i( 55 TAG, 56 String.format( 57 "connectionPids = %s\napiIntegerPids=%s\napiFloatPids = %s\n" 58 + "mIntegerCommands = %s\nmFloatCommands = %s\n", 59 connectionPids, 60 apiIntegerPids, 61 apiFloatPids, 62 mIntegerCommands, 63 mFloatCommands)); 64 } 65 generate(JsonWriter jsonWriter)66 public JsonWriter generate(JsonWriter jsonWriter) throws IOException, InterruptedException { 67 return generate(jsonWriter, SystemClock.elapsedRealtimeNanos()); 68 } 69 70 // OBD2 does not have a notion of timestamping the fault codes 71 // As such, we need to perform additional magic in order to figure out 72 // whether a fault code we retrieved is the same as a fault code we already 73 // saw in a past iteration. The logic goes as follows: 74 // for every position i in currentDtcs, if mPreviousDtcs[i] is the same 75 // fault code, then assume they are identical. If they are not the same fault code, 76 // then everything in currentDtcs[i...size()) is assumed to be a new fault code as 77 // something in the list must have moved around; if currentDtcs is shorter than 78 // mPreviousDtcs then obviously exit at the end of currentDtcs; if currentDtcs 79 // is longer, however, anything in currentDtcs past the end of mPreviousDtcs is a new 80 // fault code and will be included 81 private final class FreezeFrameIdentity { 82 public final String dtc; 83 public final int id; 84 FreezeFrameIdentity(String dtc, int id)85 FreezeFrameIdentity(String dtc, int id) { 86 this.dtc = dtc; 87 this.id = id; 88 } 89 } 90 discoverNewDtcs(List<String> currentDtcs)91 private List<FreezeFrameIdentity> discoverNewDtcs(List<String> currentDtcs) { 92 List<FreezeFrameIdentity> newDtcs = new ArrayList<>(); 93 int currentIndex = 0; 94 boolean inCopyAllMode = false; 95 96 for (; currentIndex < currentDtcs.size(); ++currentIndex) { 97 if (currentIndex == mPreviousDtcs.size()) { 98 // we have more current DTCs than previous DTCs, copy everything 99 inCopyAllMode = true; 100 break; 101 } 102 if (!currentDtcs.get(currentIndex).equals(mPreviousDtcs.get(currentIndex))) { 103 // we found a different DTC, copy everything 104 inCopyAllMode = true; 105 break; 106 } 107 // same DTC, not at end of either list yet, keep looping 108 } 109 110 if (inCopyAllMode) { 111 for (; currentIndex < currentDtcs.size(); ++currentIndex) { 112 newDtcs.add(new FreezeFrameIdentity(currentDtcs.get(currentIndex), currentIndex)); 113 } 114 } 115 116 return newDtcs; 117 } 118 generate(JsonWriter jsonWriter, long timestamp)119 public JsonWriter generate(JsonWriter jsonWriter, long timestamp) 120 throws IOException, InterruptedException { 121 List<String> currentDtcs = mConnection.getDiagnosticTroubleCodes(); 122 List<FreezeFrameIdentity> newDtcs = discoverNewDtcs(currentDtcs); 123 mPreviousDtcs = currentDtcs; 124 for (FreezeFrameIdentity freezeFrame : newDtcs) { 125 jsonWriter.beginObject(); 126 jsonWriter.name("type").value(FRAME_TYPE_FREEZE); 127 jsonWriter.name("timestamp").value(timestamp); 128 jsonWriter.name("stringValue").value(freezeFrame.dtc); 129 jsonWriter.name("intValues").beginArray(); 130 for (OutputSemanticHandler<Integer> handler : mIntegerCommands) { 131 FreezeFrameCommand<Integer> command = 132 Obd2Command.getFreezeFrameCommand(handler, freezeFrame.id); 133 try { 134 Optional<Integer> result = command.run(mConnection); 135 if (result.isPresent()) { 136 jsonWriter.beginObject(); 137 jsonWriter.name("id").value(command.getPid()); 138 jsonWriter.name("value").value(result.get()); 139 jsonWriter.endObject(); 140 } 141 } catch (IOException | InterruptedException e) { 142 Log.w( 143 TAG, 144 String.format( 145 "unable to retrieve OBD2 pid %d due to exception: %s", 146 command.getPid(), e)); 147 // skip this entry 148 } 149 } 150 jsonWriter.endArray(); 151 jsonWriter.name("floatValues").beginArray(); 152 for (OutputSemanticHandler<Float> handler : mFloatCommands) { 153 FreezeFrameCommand<Float> command = 154 Obd2Command.getFreezeFrameCommand(handler, freezeFrame.id); 155 try { 156 Optional<Float> result = command.run(mConnection); 157 if (result.isPresent()) { 158 jsonWriter.beginObject(); 159 jsonWriter.name("id").value(command.getPid()); 160 jsonWriter.name("value").value(result.get()); 161 jsonWriter.endObject(); 162 } 163 } catch (IOException | InterruptedException e) { 164 Log.w( 165 TAG, 166 String.format( 167 "unable to retrieve OBD2 pid %d due to exception: %s", 168 command.getPid(), e)); 169 // skip this entry 170 } 171 } 172 jsonWriter.endArray(); 173 jsonWriter.endObject(); 174 } 175 return jsonWriter; 176 } 177 } 178