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.hal; 18 19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 20 21 import static java.lang.Integer.toHexString; 22 23 import android.annotation.Nullable; 24 import android.car.builtin.util.Slogf; 25 import android.car.diagnostic.CarDiagnosticEvent; 26 import android.car.diagnostic.CarDiagnosticManager; 27 import android.car.hardware.CarSensorManager; 28 import android.hardware.automotive.vehicle.DiagnosticFloatSensorIndex; 29 import android.hardware.automotive.vehicle.DiagnosticIntegerSensorIndex; 30 import android.hardware.automotive.vehicle.VehicleProperty; 31 import android.hardware.automotive.vehicle.VehiclePropertyChangeMode; 32 import android.os.ServiceSpecificException; 33 import android.util.SparseArray; 34 35 import com.android.car.CarLog; 36 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 37 import com.android.internal.annotations.GuardedBy; 38 39 import java.io.PrintWriter; 40 import java.lang.reflect.Field; 41 import java.lang.reflect.Modifier; 42 import java.util.Arrays; 43 import java.util.BitSet; 44 import java.util.Collection; 45 import java.util.LinkedList; 46 import java.util.List; 47 import java.util.concurrent.CopyOnWriteArraySet; 48 49 /** 50 * Diagnostic HAL service supporting gathering diagnostic info from VHAL and translating it into 51 * higher-level semantic information 52 */ 53 public class DiagnosticHalService extends HalServiceBase { 54 static final int OBD2_SELECTIVE_FRAME_CLEAR = 1; 55 static final boolean DEBUG = false; 56 57 private static final int[] SUPPORTED_PROPERTIES = new int[]{ 58 VehicleProperty.OBD2_LIVE_FRAME, 59 VehicleProperty.OBD2_FREEZE_FRAME, 60 VehicleProperty.OBD2_FREEZE_FRAME_INFO, 61 VehicleProperty.OBD2_FREEZE_FRAME_CLEAR 62 }; 63 64 private final Object mLock = new Object(); 65 private final VehicleHal mVehicleHal; 66 private final HalPropValueBuilder mPropValueBuilder; 67 68 @GuardedBy("mLock") 69 private boolean mIsReady = false; 70 71 /** 72 * Nested class used as a place holder for vehicle HAL's diagnosed properties. 73 */ 74 public static final class DiagnosticCapabilities { 75 private final CopyOnWriteArraySet<Integer> mProperties = new CopyOnWriteArraySet<>(); 76 setSupported(int propertyId)77 void setSupported(int propertyId) { 78 mProperties.add(propertyId); 79 } 80 isSupported(int propertyId)81 boolean isSupported(int propertyId) { 82 return mProperties.contains(propertyId); 83 } 84 isLiveFrameSupported()85 public boolean isLiveFrameSupported() { 86 return isSupported(VehicleProperty.OBD2_LIVE_FRAME); 87 } 88 isFreezeFrameSupported()89 public boolean isFreezeFrameSupported() { 90 return isSupported(VehicleProperty.OBD2_FREEZE_FRAME); 91 } 92 isFreezeFrameInfoSupported()93 public boolean isFreezeFrameInfoSupported() { 94 return isSupported(VehicleProperty.OBD2_FREEZE_FRAME_INFO); 95 } 96 isFreezeFrameClearSupported()97 public boolean isFreezeFrameClearSupported() { 98 return isSupported(VehicleProperty.OBD2_FREEZE_FRAME_CLEAR); 99 } 100 isSelectiveClearFreezeFramesSupported()101 public boolean isSelectiveClearFreezeFramesSupported() { 102 return isSupported(OBD2_SELECTIVE_FRAME_CLEAR); 103 } 104 clear()105 void clear() { 106 mProperties.clear(); 107 } 108 } 109 110 @GuardedBy("mLock") 111 private final DiagnosticCapabilities mDiagnosticCapabilities = new DiagnosticCapabilities(); 112 113 @GuardedBy("mLock") 114 private DiagnosticListener mDiagnosticListener; 115 116 @GuardedBy("mLock") 117 protected final SparseArray<HalPropConfig> mVehiclePropertyToConfig = new SparseArray<>(); 118 119 @GuardedBy("mLock") 120 protected final SparseArray<HalPropConfig> mSensorTypeToConfig = new SparseArray<>(); 121 DiagnosticHalService(VehicleHal hal)122 public DiagnosticHalService(VehicleHal hal) { 123 mVehicleHal = hal; 124 mPropValueBuilder = mVehicleHal.getHalPropValueBuilder(); 125 } 126 127 @Override getAllSupportedProperties()128 public int[] getAllSupportedProperties() { 129 return SUPPORTED_PROPERTIES; 130 } 131 132 @Override takeProperties(Collection<HalPropConfig> properties)133 public void takeProperties(Collection<HalPropConfig> properties) { 134 if (DEBUG) { 135 Slogf.d(CarLog.TAG_DIAGNOSTIC, "takeSupportedProperties"); 136 } 137 for (HalPropConfig vp : properties) { 138 int sensorType = getTokenForProperty(vp); 139 if (sensorType == NOT_SUPPORTED_PROPERTY) { 140 if (DEBUG) { 141 Slogf.d(CarLog.TAG_DIAGNOSTIC, new StringBuilder() 142 .append("0x") 143 .append(toHexString(vp.getPropId())) 144 .append(" ignored") 145 .toString()); 146 } 147 } else { 148 synchronized (mLock) { 149 mSensorTypeToConfig.append(sensorType, vp); 150 } 151 } 152 } 153 } 154 155 /** 156 * Returns a unique token to be used to map this property to a higher-level sensor. 157 * 158 * This token will be stored in {@link DiagnosticHalService#mSensorTypeToConfig} to allow 159 * callers to go from unique sensor identifiers to HalPropConfig objects. 160 * 161 * <p>Possible returned tokens are: 162 * <ul> 163 * <li>{@link HalServiceBase.NOT_SUPPORTED_PROPERTY} 164 * <li>{@link CarDiagnosticManager.FRAME_TYPE_LIVE} 165 * <li>{@link CarDiagnosticManager.FRAME_TYPE_FREEZE} 166 * 167 * @param propConfig The property config 168 * @return A unique token. 169 */ getTokenForProperty(HalPropConfig propConfig)170 protected int getTokenForProperty(HalPropConfig propConfig) { 171 int propId = propConfig.getPropId(); 172 synchronized (mLock) { 173 switch (propId) { 174 case VehicleProperty.OBD2_LIVE_FRAME: 175 mDiagnosticCapabilities.setSupported(propId); 176 mVehiclePropertyToConfig.put(propId, propConfig); 177 Slogf.i(CarLog.TAG_DIAGNOSTIC, "configArray for OBD2_LIVE_FRAME is " 178 + Arrays.toString(propConfig.getConfigArray())); 179 return CarDiagnosticManager.FRAME_TYPE_LIVE; 180 case VehicleProperty.OBD2_FREEZE_FRAME: 181 mDiagnosticCapabilities.setSupported(propId); 182 mVehiclePropertyToConfig.put(propId, propConfig); 183 Slogf.i(CarLog.TAG_DIAGNOSTIC, "configArray for OBD2_FREEZE_FRAME is " 184 + Arrays.toString(propConfig.getConfigArray())); 185 return CarDiagnosticManager.FRAME_TYPE_FREEZE; 186 case VehicleProperty.OBD2_FREEZE_FRAME_INFO: 187 mDiagnosticCapabilities.setSupported(propId); 188 // We should not directly expose this to the client. This property is used 189 // only by {@link DiagnosticHalService} internally. Caller should instead use 190 // function like {@link DiagnosticHalService#getFreezeFrameTimestamps} to access 191 // frame info. 192 return NOT_SUPPORTED_PROPERTY; 193 case VehicleProperty.OBD2_FREEZE_FRAME_CLEAR: 194 mDiagnosticCapabilities.setSupported(propId); 195 int[] configArray = propConfig.getConfigArray(); 196 Slogf.i(CarLog.TAG_DIAGNOSTIC, "configArray for OBD2_FREEZE_FRAME_CLEAR is " 197 + Arrays.toString(configArray)); 198 if (configArray.length < 1) { 199 Slogf.e(CarLog.TAG_DIAGNOSTIC, "property 0x%x does not specify whether it " 200 + "supports selective clearing of freeze frames. assuming it does " 201 + "not.", propId); 202 } else { 203 if (configArray[0] == 1) { 204 mDiagnosticCapabilities.setSupported(OBD2_SELECTIVE_FRAME_CLEAR); 205 } 206 } 207 // We should not directly expose this to the client. 208 return NOT_SUPPORTED_PROPERTY; 209 default: 210 return NOT_SUPPORTED_PROPERTY; 211 } 212 } 213 } 214 215 @Override init()216 public void init() { 217 if (DEBUG) { 218 Slogf.d(CarLog.TAG_DIAGNOSTIC, "init()"); 219 } 220 synchronized (mLock) { 221 mIsReady = true; 222 } 223 } 224 225 @Override release()226 public void release() { 227 synchronized (mLock) { 228 mDiagnosticCapabilities.clear(); 229 mIsReady = false; 230 } 231 } 232 233 /** 234 * Returns the status of Diagnostic HAL. 235 * @return true if Diagnostic HAL is ready after init call. 236 */ isReady()237 public boolean isReady() { 238 synchronized (mLock) { 239 return mIsReady; 240 } 241 } 242 243 /** 244 * Returns an array of diagnostic property Ids implemented by this vehicle. 245 * 246 * @return Array of diagnostic property Ids implemented by this vehicle. Empty array if 247 * no property available. 248 */ getSupportedDiagnosticProperties()249 public int[] getSupportedDiagnosticProperties() { 250 int[] supportedDiagnosticProperties; 251 synchronized (mLock) { 252 supportedDiagnosticProperties = new int[mSensorTypeToConfig.size()]; 253 for (int i = 0; i < supportedDiagnosticProperties.length; i++) { 254 supportedDiagnosticProperties[i] = mSensorTypeToConfig.keyAt(i); 255 } 256 } 257 return supportedDiagnosticProperties; 258 } 259 260 /** 261 * Start to request diagnostic information. 262 * 263 * <p>The supported sensorTypes are one of: 264 * <ul> 265 * <li>{@link CarDiagnosticManager.FRAME_TYPE_LIVE} 266 * <li>{@link CarDiagnosticManager.FRAME_TYPE_FREEZE} 267 * 268 * <p>The supported rate are one of: 269 * <ul> 270 * <li>{@link CarSensorManager.SENSOR_RATE_ONCHANGE} 271 * <li>{@link CarSensorManager.SENSOR_RATE_NORMAL} 272 * <li>{@link CarSensorManager.SENSOR_RATE_FASTEST} 273 * <li>{@link CarSensorManager.SENSOR_RATE_FAST} 274 * <li>{@link CarSensorManager.SENSOR_RATE_UI} 275 * 276 * @param sensorType One of the supported sensor types. 277 * @param rate One of the supported rate. 278 * 279 * @return true if request successfully. otherwise return false 280 */ requestDiagnosticStart(int sensorType, int rate)281 public boolean requestDiagnosticStart(int sensorType, int rate) { 282 HalPropConfig propConfig; 283 synchronized (mLock) { 284 propConfig = mSensorTypeToConfig.get(sensorType); 285 } 286 if (propConfig == null) { 287 Slogf.e(CarLog.TAG_DIAGNOSTIC, new StringBuilder() 288 .append("Unsupported sensor type: 0x") 289 .append(toHexString(sensorType)) 290 .toString()); 291 return false; 292 } 293 int propId = propConfig.getPropId(); 294 if (DEBUG) { 295 Slogf.d(CarLog.TAG_DIAGNOSTIC, new StringBuilder() 296 .append("requestDiagnosticStart, propertyId: 0x") 297 .append(toHexString(propId)) 298 .append(", rate: ") 299 .append(rate) 300 .toString()); 301 } 302 mVehicleHal.subscribePropertySafe(this, propId, 303 fixSamplingRateForProperty(propConfig, rate)); 304 return true; 305 } 306 307 /** 308 * Stop requesting diagnostic information. 309 * @param sensorType 310 */ requestDiagnosticStop(int sensorType)311 public void requestDiagnosticStop(int sensorType) { 312 HalPropConfig propConfig; 313 synchronized (mLock) { 314 propConfig = mSensorTypeToConfig.get(sensorType); 315 } 316 if (propConfig == null) { 317 Slogf.e(CarLog.TAG_DIAGNOSTIC, new StringBuilder() 318 .append("Unsupported sensor type: 0x") 319 .append(toHexString(sensorType)) 320 .toString()); 321 return; 322 } 323 int propId = propConfig.getPropId(); 324 if (DEBUG) { 325 Slogf.d(CarLog.TAG_DIAGNOSTIC, new StringBuilder() 326 .append("requestDiagnosticStop, propertyId: 0x") 327 .append(toHexString(propId)) 328 .toString()); 329 } 330 mVehicleHal.unsubscribePropertySafe(this, propId); 331 332 } 333 334 /** 335 * Query current diagnostic value 336 * @param sensorType 337 * @return The property value. 338 */ 339 @Nullable getCurrentDiagnosticValue(int sensorType)340 public HalPropValue getCurrentDiagnosticValue(int sensorType) { 341 HalPropConfig propConfig; 342 synchronized (mLock) { 343 propConfig = mSensorTypeToConfig.get(sensorType); 344 } 345 if (propConfig == null) { 346 Slogf.e(CarLog.TAG_DIAGNOSTIC, new StringBuilder() 347 .append("property not available, sensor type: 0x") 348 .append(toHexString(sensorType)) 349 .toString()); 350 return null; 351 } 352 int propId = propConfig.getPropId(); 353 try { 354 return mVehicleHal.get(propId); 355 } catch (ServiceSpecificException e) { 356 Slogf.e(CarLog.TAG_DIAGNOSTIC, "property not ready 0x" + toHexString(propId), 357 e); 358 return null; 359 } catch (IllegalArgumentException e) { 360 Slogf.e(CarLog.TAG_DIAGNOSTIC, "illegal argument trying to read property: 0x" 361 + toHexString(propId), e); 362 return null; 363 } 364 365 } 366 getPropConfig(int halPropId)367 private HalPropConfig getPropConfig(int halPropId) { 368 HalPropConfig config; 369 synchronized (mLock) { 370 config = mVehiclePropertyToConfig.get(halPropId, null); 371 } 372 return config; 373 } 374 getPropConfigArray(int halPropId)375 private int[] getPropConfigArray(int halPropId) { 376 HalPropConfig propConfig = getPropConfig(halPropId); 377 return propConfig.getConfigArray(); 378 } 379 getLastIndex(Class<?> clazz)380 private static int getLastIndex(Class<?> clazz) { 381 int lastIndex = 0; 382 for (Field field : clazz.getDeclaredFields()) { 383 int modifiers = field.getModifiers(); 384 try { 385 if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers) 386 && Modifier.isPublic(modifiers) && field.getType().equals(int.class)) { 387 int value = field.getInt(/* object= */ null); 388 if (value > lastIndex) { 389 lastIndex = value; 390 } 391 } 392 } catch (IllegalAccessException ignored) { 393 // Ignore the exception. 394 } 395 } 396 return lastIndex; 397 } 398 getNumIntegerSensors(int halPropId)399 private int getNumIntegerSensors(int halPropId) { 400 int count = getLastIndex(DiagnosticIntegerSensorIndex.class) + 1; 401 int[] configArray = getPropConfigArray(halPropId); 402 if (configArray.length < 2) { 403 Slogf.e(CarLog.TAG_DIAGNOSTIC, "property 0x%x does not specify the number of " 404 + "vendor-specific properties. Assuming 0.", halPropId); 405 } else { 406 count += configArray[0]; 407 } 408 return count; 409 } 410 getNumFloatSensors(int halPropId)411 private int getNumFloatSensors(int halPropId) { 412 int count = getLastIndex(DiagnosticFloatSensorIndex.class) + 1; 413 int[] configArray = getPropConfigArray(halPropId); 414 if (configArray.length < 2) { 415 Slogf.e(CarLog.TAG_DIAGNOSTIC, "property 0x%x does not specify the number of " 416 + "vendor-specific properties. Assuming 0.", halPropId); 417 } else { 418 count += configArray[1]; 419 } 420 return count; 421 } 422 createCarDiagnosticEvent(HalPropValue value)423 private CarDiagnosticEvent createCarDiagnosticEvent(HalPropValue value) { 424 if (value == null) { 425 return null; 426 } 427 int propId = value.getPropId(); 428 final boolean isFreezeFrame = propId == VehicleProperty.OBD2_FREEZE_FRAME; 429 CarDiagnosticEvent.Builder builder = 430 (isFreezeFrame 431 ? CarDiagnosticEvent.Builder.newFreezeFrameBuilder() 432 : CarDiagnosticEvent.Builder.newLiveFrameBuilder()) 433 .atTimestamp(value.getTimestamp()); 434 435 BitSet bitset = BitSet.valueOf(value.getByteArray()); 436 437 int numIntegerProperties = getNumIntegerSensors(propId); 438 int numFloatProperties = getNumFloatSensors(propId); 439 440 for (int i = 0; i < numIntegerProperties; ++i) { 441 if (bitset.get(i)) { 442 builder.withIntValue(i, value.getInt32Value(i)); 443 } 444 } 445 446 for (int i = 0; i < numFloatProperties; ++i) { 447 if (bitset.get(numIntegerProperties + i)) { 448 builder.withFloatValue(i, value.getFloatValue(i)); 449 } 450 } 451 452 builder.withDtc(value.getStringValue()); 453 454 return builder.build(); 455 } 456 457 /** Listener for monitoring diagnostic event. */ 458 public interface DiagnosticListener { 459 /** 460 * Diagnostic events are available. 461 * 462 * @param events 463 */ onDiagnosticEvents(List<CarDiagnosticEvent> events)464 void onDiagnosticEvents(List<CarDiagnosticEvent> events); 465 } 466 467 // Should be used only inside handleHalEvents method. 468 private final LinkedList<CarDiagnosticEvent> mEventsToDispatch = new LinkedList<>(); 469 470 @Override onHalEvents(List<HalPropValue> values)471 public void onHalEvents(List<HalPropValue> values) { 472 for (HalPropValue value : values) { 473 CarDiagnosticEvent event = createCarDiagnosticEvent(value); 474 if (event != null) { 475 mEventsToDispatch.add(event); 476 } 477 } 478 479 DiagnosticListener listener = null; 480 synchronized (mLock) { 481 listener = mDiagnosticListener; 482 } 483 if (listener != null) { 484 listener.onDiagnosticEvents(mEventsToDispatch); 485 } 486 mEventsToDispatch.clear(); 487 } 488 489 /** 490 * Set DiagnosticListener. 491 * @param listener 492 */ setDiagnosticListener(DiagnosticListener listener)493 public void setDiagnosticListener(DiagnosticListener listener) { 494 synchronized (mLock) { 495 mDiagnosticListener = listener; 496 } 497 } 498 getDiagnosticListener()499 public DiagnosticListener getDiagnosticListener() { 500 synchronized (mLock) { 501 return mDiagnosticListener; 502 } 503 } 504 505 @Override 506 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(PrintWriter writer)507 public void dump(PrintWriter writer) { 508 writer.println("*Diagnostic HAL*"); 509 } 510 fixSamplingRateForProperty(HalPropConfig prop, int carSensorManagerRate)511 protected float fixSamplingRateForProperty(HalPropConfig prop, int carSensorManagerRate) { 512 switch (prop.getChangeMode()) { 513 case VehiclePropertyChangeMode.ON_CHANGE: 514 return 0; 515 } 516 float rate = 1.0f; 517 switch (carSensorManagerRate) { 518 case CarSensorManager.SENSOR_RATE_FASTEST: 519 case CarSensorManager.SENSOR_RATE_FAST: 520 rate = 10f; 521 break; 522 case CarSensorManager.SENSOR_RATE_UI: 523 rate = 5f; 524 break; 525 default: // fall back to default. 526 break; 527 } 528 if (rate > prop.getMaxSampleRate()) { 529 rate = prop.getMaxSampleRate(); 530 } 531 if (rate < prop.getMinSampleRate()) { 532 rate = prop.getMinSampleRate(); 533 } 534 return rate; 535 } 536 getDiagnosticCapabilities()537 public DiagnosticCapabilities getDiagnosticCapabilities() { 538 synchronized (mLock) { 539 return mDiagnosticCapabilities; 540 } 541 } 542 543 /** 544 * Returns the {@link CarDiagnosticEvent} for the current Vehicle HAL's live frame. 545 */ 546 @Nullable getCurrentLiveFrame()547 public CarDiagnosticEvent getCurrentLiveFrame() { 548 try { 549 HalPropValue value = mVehicleHal.get(VehicleProperty.OBD2_LIVE_FRAME); 550 return createCarDiagnosticEvent(value); 551 } catch (ServiceSpecificException e) { 552 Slogf.e(CarLog.TAG_DIAGNOSTIC, "Failed to read OBD2_LIVE_FRAME.", e); 553 return null; 554 } catch (IllegalArgumentException e) { 555 Slogf.e(CarLog.TAG_DIAGNOSTIC, "illegal argument trying to read OBD2_LIVE_FRAME", e); 556 return null; 557 } 558 } 559 560 /** 561 * Returns all timestamps for the Vehicle HAL's Freeze Frame data. 562 */ 563 @Nullable getFreezeFrameTimestamps()564 public long[] getFreezeFrameTimestamps() { 565 try { 566 HalPropValue value = mVehicleHal.get(VehicleProperty.OBD2_FREEZE_FRAME_INFO); 567 long[] timestamps = new long[value.getInt64ValuesSize()]; 568 for (int i = 0; i < timestamps.length; ++i) { 569 timestamps[i] = value.getInt64Value(i); 570 } 571 return timestamps; 572 } catch (ServiceSpecificException e) { 573 Slogf.e(CarLog.TAG_DIAGNOSTIC, "Failed to read OBD2_FREEZE_FRAME_INFO.", e); 574 return null; 575 } catch (IllegalArgumentException e) { 576 Slogf.e(CarLog.TAG_DIAGNOSTIC, "illegal argument trying to read OBD2_FREEZE_FRAME_INFO", 577 e); 578 return null; 579 } 580 } 581 582 /** 583 * Returns the {@link CarDiagnosticEvent} representing a Freeze Frame data for the timestamp 584 * passed as parameter. 585 */ 586 @Nullable getFreezeFrame(long timestamp)587 public CarDiagnosticEvent getFreezeFrame(long timestamp) { 588 HalPropValue getValue = mPropValueBuilder.build( 589 VehicleProperty.OBD2_FREEZE_FRAME, /*areaId=*/0, /*value=*/timestamp); 590 try { 591 HalPropValue value = mVehicleHal.get(getValue); 592 return createCarDiagnosticEvent(value); 593 } catch (ServiceSpecificException e) { 594 Slogf.e(CarLog.TAG_DIAGNOSTIC, "Failed to read OBD2_FREEZE_FRAME.", e); 595 return null; 596 } catch (IllegalArgumentException e) { 597 Slogf.e(CarLog.TAG_DIAGNOSTIC, "illegal argument trying to read OBD2_FREEZE_FRAME", e); 598 return null; 599 } 600 } 601 602 /** 603 * Clears all Vehicle HAL's Freeze Frame data for the timestamps passed as parameter. 604 */ clearFreezeFrames(long... timestamps)605 public void clearFreezeFrames(long... timestamps) { 606 HalPropValue value = mPropValueBuilder.build( 607 VehicleProperty.OBD2_FREEZE_FRAME_CLEAR, /*areaId=*/0, /*values=*/timestamps); 608 try { 609 mVehicleHal.set(value); 610 } catch (ServiceSpecificException e) { 611 Slogf.e(CarLog.TAG_DIAGNOSTIC, "Failed to write OBD2_FREEZE_FRAME_CLEAR.", e); 612 } catch (IllegalArgumentException e) { 613 Slogf.e(CarLog.TAG_DIAGNOSTIC, "illegal argument trying to write " 614 + "OBD2_FREEZE_FRAME_CLEAR", e); 615 } 616 } 617 } 618