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.diagnostic.CarDiagnosticEvent; 25 import android.car.diagnostic.CarDiagnosticManager; 26 import android.car.hardware.CarSensorManager; 27 import android.hardware.automotive.vehicle.V2_0.DiagnosticFloatSensorIndex; 28 import android.hardware.automotive.vehicle.V2_0.DiagnosticIntegerSensorIndex; 29 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig; 30 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue; 31 import android.hardware.automotive.vehicle.V2_0.VehicleProperty; 32 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode; 33 import android.os.ServiceSpecificException; 34 import android.util.Slog; 35 import android.util.SparseArray; 36 37 import com.android.car.CarLog; 38 import com.android.car.CarServiceUtils; 39 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 40 import com.android.car.vehiclehal.VehiclePropValueBuilder; 41 import com.android.internal.annotations.GuardedBy; 42 43 import java.io.PrintWriter; 44 import java.util.BitSet; 45 import java.util.Collection; 46 import java.util.LinkedList; 47 import java.util.List; 48 import java.util.concurrent.CopyOnWriteArraySet; 49 50 /** 51 * Diagnostic HAL service supporting gathering diagnostic info from VHAL and translating it into 52 * higher-level semantic information 53 */ 54 public class DiagnosticHalService extends HalServiceBase { 55 static final int OBD2_SELECTIVE_FRAME_CLEAR = 1; 56 static final boolean DEBUG = false; 57 58 private static final int[] SUPPORTED_PROPERTIES = new int[]{ 59 VehicleProperty.OBD2_LIVE_FRAME, 60 VehicleProperty.OBD2_FREEZE_FRAME, 61 VehicleProperty.OBD2_FREEZE_FRAME_INFO, 62 VehicleProperty.OBD2_FREEZE_FRAME_CLEAR 63 }; 64 65 private final Object mLock = new Object(); 66 private final VehicleHal mVehicleHal; 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<VehiclePropConfig> mVehiclePropertyToConfig = new SparseArray<>(); 118 119 @GuardedBy("mLock") 120 protected final SparseArray<VehiclePropConfig> mSensorTypeToConfig = new SparseArray<>(); 121 DiagnosticHalService(VehicleHal hal)122 public DiagnosticHalService(VehicleHal hal) { 123 mVehicleHal = hal; 124 125 } 126 127 @Override getAllSupportedProperties()128 public int[] getAllSupportedProperties() { 129 return SUPPORTED_PROPERTIES; 130 } 131 132 @Override takeProperties(Collection<VehiclePropConfig> properties)133 public void takeProperties(Collection<VehiclePropConfig> properties) { 134 if (DEBUG) { 135 Slog.d(CarLog.TAG_DIAGNOSTIC, "takeSupportedProperties"); 136 } 137 for (VehiclePropConfig vp : properties) { 138 int sensorType = getTokenForProperty(vp); 139 if (sensorType == NOT_SUPPORTED_PROPERTY) { 140 if (DEBUG) { 141 Slog.d(CarLog.TAG_DIAGNOSTIC, new StringBuilder() 142 .append("0x") 143 .append(toHexString(vp.prop)) 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 * This token will be stored in {@link DiagnosticHalService#mSensorTypeToConfig} to allow 158 * callers to go from unique sensor identifiers to VehiclePropConfig objects 159 * @param propConfig 160 * @return SENSOR_TYPE_INVALID or a locally unique token 161 */ getTokenForProperty(VehiclePropConfig propConfig)162 protected int getTokenForProperty(VehiclePropConfig propConfig) { 163 switch (propConfig.prop) { 164 case VehicleProperty.OBD2_LIVE_FRAME: 165 mDiagnosticCapabilities.setSupported(propConfig.prop); 166 mVehiclePropertyToConfig.put(propConfig.prop, propConfig); 167 Slog.i(CarLog.TAG_DIAGNOSTIC, "configArray for OBD2_LIVE_FRAME is " 168 + propConfig.configArray); 169 return CarDiagnosticManager.FRAME_TYPE_LIVE; 170 case VehicleProperty.OBD2_FREEZE_FRAME: 171 mDiagnosticCapabilities.setSupported(propConfig.prop); 172 mVehiclePropertyToConfig.put(propConfig.prop, propConfig); 173 Slog.i(CarLog.TAG_DIAGNOSTIC, "configArray for OBD2_FREEZE_FRAME is " 174 + propConfig.configArray); 175 return CarDiagnosticManager.FRAME_TYPE_FREEZE; 176 case VehicleProperty.OBD2_FREEZE_FRAME_INFO: 177 mDiagnosticCapabilities.setSupported(propConfig.prop); 178 return propConfig.prop; 179 case VehicleProperty.OBD2_FREEZE_FRAME_CLEAR: 180 mDiagnosticCapabilities.setSupported(propConfig.prop); 181 Slog.i(CarLog.TAG_DIAGNOSTIC, "configArray for OBD2_FREEZE_FRAME_CLEAR is " 182 + propConfig.configArray); 183 if (propConfig.configArray.size() < 1) { 184 Slog.e(CarLog.TAG_DIAGNOSTIC, String.format( 185 "property 0x%x does not specify whether it supports selective " 186 + "clearing of freeze frames. assuming it does not.", 187 propConfig.prop)); 188 } else { 189 if (propConfig.configArray.get(0) == 1) { 190 mDiagnosticCapabilities.setSupported(OBD2_SELECTIVE_FRAME_CLEAR); 191 } 192 } 193 return propConfig.prop; 194 default: 195 return NOT_SUPPORTED_PROPERTY; 196 } 197 } 198 199 @Override init()200 public void init() { 201 if (DEBUG) { 202 Slog.d(CarLog.TAG_DIAGNOSTIC, "init()"); 203 } 204 synchronized (mLock) { 205 mIsReady = true; 206 } 207 } 208 209 @Override release()210 public void release() { 211 synchronized (mLock) { 212 mDiagnosticCapabilities.clear(); 213 mIsReady = false; 214 } 215 } 216 217 /** 218 * Returns the status of Diagnostic HAL. 219 * @return true if Diagnostic HAL is ready after init call. 220 */ isReady()221 public boolean isReady() { 222 return mIsReady; 223 } 224 225 /** 226 * Returns an array of diagnostic property Ids implemented by this vehicle. 227 * 228 * @return Array of diagnostic property Ids implemented by this vehicle. Empty array if 229 * no property available. 230 */ getSupportedDiagnosticProperties()231 public int[] getSupportedDiagnosticProperties() { 232 int[] supportedDiagnosticProperties; 233 synchronized (mLock) { 234 supportedDiagnosticProperties = new int[mSensorTypeToConfig.size()]; 235 for (int i = 0; i < supportedDiagnosticProperties.length; i++) { 236 supportedDiagnosticProperties[i] = mSensorTypeToConfig.keyAt(i); 237 } 238 } 239 return supportedDiagnosticProperties; 240 } 241 242 /** 243 * Start to request diagnostic information. 244 * @param sensorType 245 * @param rate 246 * @return true if request successfully. otherwise return false 247 */ requestDiagnosticStart(int sensorType, int rate)248 public boolean requestDiagnosticStart(int sensorType, int rate) { 249 VehiclePropConfig propConfig; 250 synchronized (mLock) { 251 propConfig = mSensorTypeToConfig.get(sensorType); 252 } 253 if (propConfig == null) { 254 Slog.e(CarLog.TAG_DIAGNOSTIC, new StringBuilder() 255 .append("VehiclePropConfig not found, propertyId: 0x") 256 .append(toHexString(propConfig.prop)) 257 .toString()); 258 return false; 259 } 260 if (DEBUG) { 261 Slog.d(CarLog.TAG_DIAGNOSTIC, new StringBuilder() 262 .append("requestDiagnosticStart, propertyId: 0x") 263 .append(toHexString(propConfig.prop)) 264 .append(", rate: ") 265 .append(rate) 266 .toString()); 267 } 268 mVehicleHal.subscribeProperty(this, propConfig.prop, 269 fixSamplingRateForProperty(propConfig, rate)); 270 return true; 271 } 272 273 /** 274 * Stop requesting diagnostic information. 275 * @param sensorType 276 */ requestDiagnosticStop(int sensorType)277 public void requestDiagnosticStop(int sensorType) { 278 VehiclePropConfig propConfig; 279 synchronized (mLock) { 280 propConfig = mSensorTypeToConfig.get(sensorType); 281 } 282 if (propConfig == null) { 283 Slog.e(CarLog.TAG_DIAGNOSTIC, new StringBuilder() 284 .append("VehiclePropConfig not found, propertyId: 0x") 285 .append(toHexString(propConfig.prop)) 286 .toString()); 287 return; 288 } 289 if (DEBUG) { 290 Slog.d(CarLog.TAG_DIAGNOSTIC, new StringBuilder() 291 .append("requestDiagnosticStop, propertyId: 0x") 292 .append(toHexString(propConfig.prop)) 293 .toString()); 294 } 295 mVehicleHal.unsubscribeProperty(this, propConfig.prop); 296 297 } 298 299 /** 300 * Query current diagnostic value 301 * @param sensorType 302 * @return VehiclePropValue of the property 303 */ 304 @Nullable getCurrentDiagnosticValue(int sensorType)305 public VehiclePropValue getCurrentDiagnosticValue(int sensorType) { 306 VehiclePropConfig propConfig; 307 synchronized (mLock) { 308 propConfig = mSensorTypeToConfig.get(sensorType); 309 } 310 if (propConfig == null) { 311 Slog.e(CarLog.TAG_DIAGNOSTIC, new StringBuilder() 312 .append("property not available 0x") 313 .append(toHexString(propConfig.prop)) 314 .toString()); 315 return null; 316 } 317 try { 318 return mVehicleHal.get(propConfig.prop); 319 } catch (ServiceSpecificException e) { 320 Slog.e(CarLog.TAG_DIAGNOSTIC, 321 "property not ready 0x" + toHexString(propConfig.prop), e); 322 return null; 323 } 324 325 } 326 getPropConfig(int halPropId)327 private VehiclePropConfig getPropConfig(int halPropId) { 328 VehiclePropConfig config; 329 synchronized (mLock) { 330 config = mVehiclePropertyToConfig.get(halPropId, null); 331 } 332 return config; 333 } 334 getPropConfigArray(int halPropId)335 private List<Integer> getPropConfigArray(int halPropId) { 336 VehiclePropConfig propConfig = getPropConfig(halPropId); 337 return propConfig.configArray; 338 } 339 getNumIntegerSensors(int halPropId)340 private int getNumIntegerSensors(int halPropId) { 341 int count = DiagnosticIntegerSensorIndex.LAST_SYSTEM_INDEX + 1; 342 List<Integer> configArray = getPropConfigArray(halPropId); 343 if (configArray.size() < 2) { 344 Slog.e(CarLog.TAG_DIAGNOSTIC, String.format( 345 "property 0x%x does not specify the number of vendor-specific properties." 346 + "assuming 0.", halPropId)); 347 } else { 348 count += configArray.get(0); 349 } 350 return count; 351 } 352 getNumFloatSensors(int halPropId)353 private int getNumFloatSensors(int halPropId) { 354 int count = DiagnosticFloatSensorIndex.LAST_SYSTEM_INDEX + 1; 355 List<Integer> configArray = getPropConfigArray(halPropId); 356 if (configArray.size() < 2) { 357 Slog.e(CarLog.TAG_DIAGNOSTIC, String.format( 358 "property 0x%x does not specify the number of vendor-specific properties." 359 + "assuming 0.", halPropId)); 360 } else { 361 count += configArray.get(1); 362 } 363 return count; 364 } 365 createCarDiagnosticEvent(VehiclePropValue value)366 private CarDiagnosticEvent createCarDiagnosticEvent(VehiclePropValue value) { 367 if (value == null) return null; 368 final boolean isFreezeFrame = value.prop == VehicleProperty.OBD2_FREEZE_FRAME; 369 CarDiagnosticEvent.Builder builder = 370 (isFreezeFrame 371 ? CarDiagnosticEvent.Builder.newFreezeFrameBuilder() 372 : CarDiagnosticEvent.Builder.newLiveFrameBuilder()) 373 .atTimestamp(value.timestamp); 374 375 BitSet bitset = BitSet.valueOf(CarServiceUtils.toByteArray(value.value.bytes)); 376 377 int numIntegerProperties = getNumIntegerSensors(value.prop); 378 int numFloatProperties = getNumFloatSensors(value.prop); 379 380 for (int i = 0; i < numIntegerProperties; ++i) { 381 if (bitset.get(i)) { 382 builder.withIntValue(i, value.value.int32Values.get(i)); 383 } 384 } 385 386 for (int i = 0; i < numFloatProperties; ++i) { 387 if (bitset.get(numIntegerProperties + i)) { 388 builder.withFloatValue(i, value.value.floatValues.get(i)); 389 } 390 } 391 392 builder.withDtc(value.value.stringValue); 393 394 return builder.build(); 395 } 396 397 /** Listener for monitoring diagnostic event. */ 398 public interface DiagnosticListener { 399 /** 400 * Diagnostic events are available. 401 * 402 * @param events 403 */ onDiagnosticEvents(List<CarDiagnosticEvent> events)404 void onDiagnosticEvents(List<CarDiagnosticEvent> events); 405 } 406 407 // Should be used only inside handleHalEvents method. 408 private final LinkedList<CarDiagnosticEvent> mEventsToDispatch = new LinkedList<>(); 409 410 @Override onHalEvents(List<VehiclePropValue> values)411 public void onHalEvents(List<VehiclePropValue> values) { 412 for (VehiclePropValue value : values) { 413 CarDiagnosticEvent event = createCarDiagnosticEvent(value); 414 if (event != null) { 415 mEventsToDispatch.add(event); 416 } 417 } 418 419 DiagnosticListener listener = null; 420 synchronized (mLock) { 421 listener = mDiagnosticListener; 422 } 423 if (listener != null) { 424 listener.onDiagnosticEvents(mEventsToDispatch); 425 } 426 mEventsToDispatch.clear(); 427 } 428 429 /** 430 * Set DiagnosticListener. 431 * @param listener 432 */ setDiagnosticListener(DiagnosticListener listener)433 public void setDiagnosticListener(DiagnosticListener listener) { 434 synchronized (mLock) { 435 mDiagnosticListener = listener; 436 } 437 } 438 getDiagnosticListener()439 public DiagnosticListener getDiagnosticListener() { 440 return mDiagnosticListener; 441 } 442 443 @Override 444 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(PrintWriter writer)445 public void dump(PrintWriter writer) { 446 writer.println("*Diagnostic HAL*"); 447 } 448 fixSamplingRateForProperty(VehiclePropConfig prop, int carSensorManagerRate)449 protected float fixSamplingRateForProperty(VehiclePropConfig prop, int carSensorManagerRate) { 450 switch (prop.changeMode) { 451 case VehiclePropertyChangeMode.ON_CHANGE: 452 return 0; 453 } 454 float rate = 1.0f; 455 switch (carSensorManagerRate) { 456 case CarSensorManager.SENSOR_RATE_FASTEST: 457 case CarSensorManager.SENSOR_RATE_FAST: 458 rate = 10f; 459 break; 460 case CarSensorManager.SENSOR_RATE_UI: 461 rate = 5f; 462 break; 463 default: // fall back to default. 464 break; 465 } 466 if (rate > prop.maxSampleRate) { 467 rate = prop.maxSampleRate; 468 } 469 if (rate < prop.minSampleRate) { 470 rate = prop.minSampleRate; 471 } 472 return rate; 473 } 474 getDiagnosticCapabilities()475 public DiagnosticCapabilities getDiagnosticCapabilities() { 476 return mDiagnosticCapabilities; 477 } 478 479 /** 480 * Returns the {@link CarDiagnosticEvent} for the current Vehicle HAL's live frame. 481 */ 482 @Nullable getCurrentLiveFrame()483 public CarDiagnosticEvent getCurrentLiveFrame() { 484 try { 485 VehiclePropValue value = mVehicleHal.get(VehicleProperty.OBD2_LIVE_FRAME); 486 return createCarDiagnosticEvent(value); 487 } catch (ServiceSpecificException e) { 488 Slog.e(CarLog.TAG_DIAGNOSTIC, "Failed to read OBD2_LIVE_FRAME.", e); 489 return null; 490 } catch (IllegalArgumentException e) { 491 Slog.e(CarLog.TAG_DIAGNOSTIC, "illegal argument trying to read OBD2_LIVE_FRAME", e); 492 return null; 493 } 494 } 495 496 /** 497 * Returns all timestamps for the Vehicle HAL's Freeze Frame data. 498 */ 499 @Nullable getFreezeFrameTimestamps()500 public long[] getFreezeFrameTimestamps() { 501 try { 502 VehiclePropValue value = mVehicleHal.get(VehicleProperty.OBD2_FREEZE_FRAME_INFO); 503 long[] timestamps = new long[value.value.int64Values.size()]; 504 for (int i = 0; i < timestamps.length; ++i) { 505 timestamps[i] = value.value.int64Values.get(i); 506 } 507 return timestamps; 508 } catch (ServiceSpecificException e) { 509 Slog.e(CarLog.TAG_DIAGNOSTIC, "Failed to read OBD2_FREEZE_FRAME_INFO.", e); 510 return null; 511 } catch (IllegalArgumentException e) { 512 Slog.e(CarLog.TAG_DIAGNOSTIC, 513 "illegal argument trying to read OBD2_FREEZE_FRAME_INFO", e); 514 return null; 515 } 516 } 517 518 /** 519 * Returns the {@link CarDiagnosticEvent} representing a Freeze Frame data for the timestamp 520 * passed as parameter. 521 */ 522 @Nullable getFreezeFrame(long timestamp)523 public CarDiagnosticEvent getFreezeFrame(long timestamp) { 524 VehiclePropValueBuilder builder = VehiclePropValueBuilder.newBuilder( 525 VehicleProperty.OBD2_FREEZE_FRAME); 526 builder.setInt64Value(timestamp); 527 try { 528 VehiclePropValue value = mVehicleHal.get(builder.build()); 529 return createCarDiagnosticEvent(value); 530 } catch (ServiceSpecificException e) { 531 Slog.e(CarLog.TAG_DIAGNOSTIC, "Failed to read OBD2_FREEZE_FRAME.", e); 532 return null; 533 } catch (IllegalArgumentException e) { 534 Slog.e(CarLog.TAG_DIAGNOSTIC, 535 "illegal argument trying to read OBD2_FREEZE_FRAME", e); 536 return null; 537 } 538 } 539 540 /** 541 * Clears all Vehicle HAL's Freeze Frame data for the timestamps passed as parameter. 542 */ clearFreezeFrames(long... timestamps)543 public void clearFreezeFrames(long... timestamps) { 544 VehiclePropValueBuilder builder = VehiclePropValueBuilder.newBuilder( 545 VehicleProperty.OBD2_FREEZE_FRAME_CLEAR); 546 builder.setInt64Value(timestamps); 547 try { 548 mVehicleHal.set(builder.build()); 549 } catch (ServiceSpecificException e) { 550 Slog.e(CarLog.TAG_DIAGNOSTIC, "Failed to write OBD2_FREEZE_FRAME_CLEAR.", e); 551 } catch (IllegalArgumentException e) { 552 Slog.e(CarLog.TAG_DIAGNOSTIC, 553 "illegal argument trying to write OBD2_FREEZE_FRAME_CLEAR", e); 554 } 555 } 556 } 557