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