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