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