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