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
17package com.android.car.hal;
18
19import android.annotation.Nullable;
20import android.car.diagnostic.CarDiagnosticEvent;
21import android.car.diagnostic.CarDiagnosticManager;
22import android.car.hardware.CarSensorManager;
23import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
24import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
25import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode;
26import android.hardware.automotive.vehicle.V2_0.DiagnosticFloatSensorIndex;
27import android.hardware.automotive.vehicle.V2_0.DiagnosticIntegerSensorIndex;
28import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
29import android.util.Log;
30import android.util.SparseArray;
31import com.android.car.CarLog;
32import com.android.car.CarServiceUtils;
33import com.android.car.vehiclehal.VehiclePropValueBuilder;
34import java.io.PrintWriter;
35import java.util.BitSet;
36import java.util.LinkedList;
37import java.util.List;
38import java.util.concurrent.CopyOnWriteArraySet;
39
40/**
41 * Diagnostic HAL service supporting gathering diagnostic info from VHAL and translating it into
42 * higher-level semantic information
43 */
44public class DiagnosticHalService extends SensorHalServiceBase {
45    public static class DiagnosticCapabilities {
46        private final CopyOnWriteArraySet<Integer> mProperties = new CopyOnWriteArraySet<>();
47
48        void setSupported(int propertyId) {
49            mProperties.add(propertyId);
50        }
51
52        boolean isSupported(int propertyId) {
53            return mProperties.contains(propertyId);
54        }
55
56        public boolean isLiveFrameSupported() {
57            return isSupported(VehicleProperty.OBD2_LIVE_FRAME);
58        }
59
60        public boolean isFreezeFrameSupported() {
61            return isSupported(VehicleProperty.OBD2_FREEZE_FRAME);
62        }
63
64        public boolean isFreezeFrameInfoSupported() {
65            return isSupported(VehicleProperty.OBD2_FREEZE_FRAME_INFO);
66        }
67
68        public boolean isFreezeFrameClearSupported() {
69            return isSupported(VehicleProperty.OBD2_FREEZE_FRAME_CLEAR);
70        }
71
72        void clear() {
73            mProperties.clear();
74        }
75    }
76
77    private final DiagnosticCapabilities mDiagnosticCapabilities = new DiagnosticCapabilities();
78    private DiagnosticListener mDiagnosticListener;
79    protected final SparseArray<VehiclePropConfig> mVehiclePropertyToConfig = new SparseArray<>();
80
81    public DiagnosticHalService(VehicleHal hal) {
82        super(hal);
83    }
84
85    @Override
86    protected int getTokenForProperty(VehiclePropConfig propConfig) {
87        switch (propConfig.prop) {
88            case VehicleProperty.OBD2_LIVE_FRAME:
89                mDiagnosticCapabilities.setSupported(propConfig.prop);
90                mVehiclePropertyToConfig.put(propConfig.prop, propConfig);
91                Log.i(CarLog.TAG_DIAGNOSTIC, String.format("configArray for OBD2_LIVE_FRAME is %s",
92                    propConfig.configArray));
93                return CarDiagnosticManager.FRAME_TYPE_LIVE;
94            case VehicleProperty.OBD2_FREEZE_FRAME:
95                mDiagnosticCapabilities.setSupported(propConfig.prop);
96                mVehiclePropertyToConfig.put(propConfig.prop, propConfig);
97                Log.i(CarLog.TAG_DIAGNOSTIC, String.format("configArray for OBD2_FREEZE_FRAME is %s",
98                    propConfig.configArray));
99                return CarDiagnosticManager.FRAME_TYPE_FREEZE;
100            case VehicleProperty.OBD2_FREEZE_FRAME_INFO:
101                mDiagnosticCapabilities.setSupported(propConfig.prop);
102                return propConfig.prop;
103            case VehicleProperty.OBD2_FREEZE_FRAME_CLEAR:
104                mDiagnosticCapabilities.setSupported(propConfig.prop);
105                return propConfig.prop;
106            default:
107                return SENSOR_TYPE_INVALID;
108        }
109    }
110
111    @Override
112    public synchronized void release() {
113        super.release();
114        mDiagnosticCapabilities.clear();
115    }
116
117    private VehiclePropConfig getPropConfig(int halPropId) {
118        return mVehiclePropertyToConfig.get(halPropId, null);
119    }
120
121    private List<Integer> getPropConfigArray(int halPropId) {
122        VehiclePropConfig propConfig = getPropConfig(halPropId);
123        return propConfig.configArray;
124    }
125
126    private int getNumIntegerSensors(int halPropId) {
127        int count = DiagnosticIntegerSensorIndex.LAST_SYSTEM_INDEX + 1;
128        List<Integer> configArray = getPropConfigArray(halPropId);
129        if(configArray.size() < 2) {
130            Log.e(CarLog.TAG_DIAGNOSTIC, String.format(
131                    "property 0x%x does not specify the number of vendor-specific properties." +
132                            "assuming 0.", halPropId));
133        }
134        else {
135            count += configArray.get(0);
136        }
137        return count;
138    }
139
140    private int getNumFloatSensors(int halPropId) {
141        int count = DiagnosticFloatSensorIndex.LAST_SYSTEM_INDEX + 1;
142        List<Integer> configArray = getPropConfigArray(halPropId);
143        if(configArray.size() < 2) {
144            Log.e(CarLog.TAG_DIAGNOSTIC, String.format(
145                "property 0x%x does not specify the number of vendor-specific properties." +
146                    "assuming 0.", halPropId));
147        }
148        else {
149            count += configArray.get(1);
150        }
151        return count;
152    }
153
154    private CarDiagnosticEvent createCarDiagnosticEvent(VehiclePropValue value) {
155        if (null == value)
156            return null;
157
158        final boolean isFreezeFrame = value.prop == VehicleProperty.OBD2_FREEZE_FRAME;
159
160        CarDiagnosticEvent.Builder builder =
161                (isFreezeFrame
162                                ? CarDiagnosticEvent.Builder.newFreezeFrameBuilder()
163                                : CarDiagnosticEvent.Builder.newLiveFrameBuilder())
164                        .atTimestamp(value.timestamp);
165
166        BitSet bitset = BitSet.valueOf(CarServiceUtils.toByteArray(value.value.bytes));
167
168        int numIntegerProperties = getNumIntegerSensors(value.prop);
169        int numFloatProperties = getNumFloatSensors(value.prop);
170
171        for (int i = 0; i < numIntegerProperties; ++i) {
172            if (bitset.get(i)) {
173                builder.withIntValue(i, value.value.int32Values.get(i));
174            }
175        }
176
177        for (int i = 0; i < numFloatProperties; ++i) {
178            if (bitset.get(numIntegerProperties + i)) {
179                builder.withFloatValue(i, value.value.floatValues.get(i));
180            }
181        }
182
183        builder.withDtc(value.value.stringValue);
184
185        return builder.build();
186    }
187
188    /** Listener for monitoring diagnostic event. */
189    public interface DiagnosticListener {
190        /**
191         * Diagnostic events are available.
192         *
193         * @param events
194         */
195        void onDiagnosticEvents(List<CarDiagnosticEvent> events);
196    }
197
198    // Should be used only inside handleHalEvents method.
199    private final LinkedList<CarDiagnosticEvent> mEventsToDispatch = new LinkedList<>();
200
201    @Override
202    public void handleHalEvents(List<VehiclePropValue> values) {
203        for (VehiclePropValue value : values) {
204            CarDiagnosticEvent event = createCarDiagnosticEvent(value);
205            if (event != null) {
206                mEventsToDispatch.add(event);
207            }
208        }
209
210        DiagnosticListener listener = null;
211        synchronized (this) {
212            listener = mDiagnosticListener;
213        }
214        if (listener != null) {
215            listener.onDiagnosticEvents(mEventsToDispatch);
216        }
217        mEventsToDispatch.clear();
218    }
219
220    public synchronized void setDiagnosticListener(DiagnosticListener listener) {
221        mDiagnosticListener = listener;
222    }
223
224    public DiagnosticListener getDiagnosticListener() {
225        return mDiagnosticListener;
226    }
227
228    @Override
229    public void dump(PrintWriter writer) {
230        writer.println("*Diagnostic HAL*");
231    }
232
233    @Override
234    protected float fixSamplingRateForProperty(VehiclePropConfig prop, int carSensorManagerRate) {
235        //TODO(egranata): tweak this for diagnostics
236        switch (prop.changeMode) {
237            case VehiclePropertyChangeMode.ON_CHANGE:
238            case VehiclePropertyChangeMode.ON_SET:
239                return 0;
240        }
241        float rate = 1.0f;
242        switch (carSensorManagerRate) {
243            case CarSensorManager.SENSOR_RATE_FASTEST:
244            case CarSensorManager.SENSOR_RATE_FAST:
245                rate = 10f;
246                break;
247            case CarSensorManager.SENSOR_RATE_UI:
248                rate = 5f;
249                break;
250            default: // fall back to default.
251                break;
252        }
253        if (rate > prop.maxSampleRate) {
254            rate = prop.maxSampleRate;
255        }
256        if (rate < prop.minSampleRate) {
257            rate = prop.minSampleRate;
258        }
259        return rate;
260    }
261
262    public DiagnosticCapabilities getDiagnosticCapabilities() {
263        return mDiagnosticCapabilities;
264    }
265
266    @Nullable
267    public CarDiagnosticEvent getCurrentLiveFrame() {
268        try {
269            VehiclePropValue value = mHal.get(VehicleProperty.OBD2_LIVE_FRAME);
270            return createCarDiagnosticEvent(value);
271        } catch (PropertyTimeoutException e) {
272            Log.e(CarLog.TAG_DIAGNOSTIC, "timeout trying to read OBD2_LIVE_FRAME");
273            return null;
274        } catch (IllegalArgumentException e) {
275            Log.e(CarLog.TAG_DIAGNOSTIC, "illegal argument trying to read OBD2_LIVE_FRAME", e);
276            return null;
277        }
278    }
279
280    @Nullable
281    public long[] getFreezeFrameTimestamps() {
282        try {
283            VehiclePropValue value = mHal.get(VehicleProperty.OBD2_FREEZE_FRAME_INFO);
284            long[] timestamps = new long[value.value.int64Values.size()];
285            for (int i = 0; i < timestamps.length; ++i) {
286                timestamps[i] = value.value.int64Values.get(i);
287            }
288            return timestamps;
289        } catch (PropertyTimeoutException e) {
290            Log.e(CarLog.TAG_DIAGNOSTIC, "timeout trying to read OBD2_FREEZE_FRAME_INFO");
291            return null;
292        } catch (IllegalArgumentException e) {
293            Log.e(CarLog.TAG_DIAGNOSTIC,
294                    "illegal argument trying to read OBD2_FREEZE_FRAME_INFO", e);
295            return null;
296        }
297    }
298
299    @Nullable
300    public CarDiagnosticEvent getFreezeFrame(long timestamp) {
301        VehiclePropValueBuilder builder = VehiclePropValueBuilder.newBuilder(
302            VehicleProperty.OBD2_FREEZE_FRAME);
303        builder.setInt64Value(timestamp);
304        try {
305            VehiclePropValue value = mHal.get(builder.build());
306            return createCarDiagnosticEvent(value);
307        } catch (PropertyTimeoutException e) {
308            Log.e(CarLog.TAG_DIAGNOSTIC, "timeout trying to read OBD2_FREEZE_FRAME");
309            return null;
310        } catch (IllegalArgumentException e) {
311            Log.e(CarLog.TAG_DIAGNOSTIC,
312                    "illegal argument trying to read OBD2_FREEZE_FRAME", e);
313            return null;
314        }
315    }
316
317    public void clearFreezeFrames(long... timestamps) {
318        VehiclePropValueBuilder builder = VehiclePropValueBuilder.newBuilder(
319            VehicleProperty.OBD2_FREEZE_FRAME_CLEAR);
320        builder.setInt64Value(timestamps);
321        try {
322            mHal.set(builder.build());
323        } catch (PropertyTimeoutException e) {
324            Log.e(CarLog.TAG_DIAGNOSTIC, "timeout trying to write OBD2_FREEZE_FRAME_CLEAR");
325        } catch (IllegalArgumentException e) {
326            Log.e(CarLog.TAG_DIAGNOSTIC,
327                "illegal argument trying to write OBD2_FREEZE_FRAME_CLEAR", e);
328        }
329    }
330}
331