1#!/usr/bin/env python
2#
3# Copyright (C) 2016 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18import logging
19import time
20
21from vts.runners.host import asserts
22from vts.runners.host import const
23from vts.runners.host import test_runner
24from vts.testcases.template.hal_hidl_host_test import hal_hidl_host_test
25
26VEHICLE_V2_0_HAL = "android.hardware.automotive.vehicle@2.0::IVehicle"
27
28
29class VtsHalAutomotiveVehicleV2_0HostTest(hal_hidl_host_test.HalHidlHostTest):
30    """A simple testcase for the VEHICLE HIDL HAL.
31
32    Attributes:
33        _arrived: boolean, the flag of onPropertyEvent received.
34        onPropertyEventCalled: integer, the number of onPropertyEvent received.
35        onPropertySetErrorCalled: integer, the number of onPropertySetError
36        received.
37        DEVICE_TMP_DIR: string, target device's tmp directory path.
38    """
39
40    TEST_HAL_SERVICES = {
41        VEHICLE_V2_0_HAL,
42    }
43    DEVICE_TMP_DIR = "/data/local/tmp"
44
45    def setUpClass(self):
46        """Creates a mirror and init vehicle hal."""
47        super(VtsHalAutomotiveVehicleV2_0HostTest, self).setUpClass()
48
49        results = self.shell.Execute("id -u system")
50        system_uid = results[const.STDOUT][0].strip()
51        logging.info("system_uid: %s", system_uid)
52
53        self.dut.hal.InitHidlHal(
54            target_type="vehicle",
55            target_basepaths=self.dut.libPaths,
56            target_version=2.0,
57            target_package="android.hardware.automotive.vehicle",
58            target_component_name="IVehicle",
59            hw_binder_service_name=self.getHalServiceName(VEHICLE_V2_0_HAL),
60            bits=int(self.abi_bitness))
61
62        self.vehicle = self.dut.hal.vehicle  # shortcut
63        self.vehicle.SetCallerUid(system_uid)
64        self.vtypes = self.dut.hal.vehicle.GetHidlTypeInterface("types")
65        logging.info("vehicle types: %s", self.vtypes)
66        asserts.assertEqual(0x00ff0000, self.vtypes.VehiclePropertyType.MASK)
67        asserts.assertEqual(0x0f000000, self.vtypes.VehicleArea.MASK)
68
69        self.propToConfig = {}
70        for config in self.vehicle.getAllPropConfigs():
71            self.propToConfig[config['prop']] = config
72        self.configList = self.propToConfig.values()
73
74    def tearDownClass(self):
75        """Performs clean-up pushed file"""
76
77        cmd_results = self.shell.Execute("rm -rf %s" % self.DEVICE_TMP_DIR)
78        if not cmd_results or any(cmd_results[const.EXIT_CODE]):
79            logging.info("Failed to remove: %s", cmd_results)
80        super(VtsHalAutomotiveVehicleV2_0HostTest, self).tearDownClass()
81
82    def testListProperties(self):
83        """Checks whether some PropConfigs are returned.
84
85        Verifies that call to getAllPropConfigs is not failing and
86        it returns at least 1 vehicle property config.
87        """
88        logging.info("all supported properties: %s", self.configList)
89        asserts.assertLess(0, len(self.configList))
90
91    def emptyValueProperty(self, propertyId, areaId=0):
92        """Creates a property structure for use with the Vehicle HAL.
93
94        Args:
95            propertyId: the numeric identifier of the output property.
96            areaId: the numeric identifier of the vehicle area of the output
97                    property. 0, or omitted, for global.
98
99        Returns:
100            a property structure for use with the Vehicle HAL.
101        """
102        return {
103            'prop': propertyId,
104            'timestamp': 0,
105            'areaId': areaId,
106            'status': self.vtypes.VehiclePropertyStatus.AVAILABLE,
107            'value': {
108                'int32Values': [],
109                'floatValues': [],
110                'int64Values': [],
111                'bytes': [],
112                'stringValue': ""
113            }
114        }
115
116    def readVhalProperty(self, propertyId, areaId=0):
117        """Reads a specified property from Vehicle HAL.
118
119        Args:
120            propertyId: the numeric identifier of the property to be read.
121            areaId: the numeric identifier of the vehicle area to retrieve the
122                    property for. 0, or omitted, for global.
123
124        Returns:
125            the value of the property as read from Vehicle HAL, or None
126            if it could not read successfully.
127        """
128        vp = self.vtypes.Py2Pb("VehiclePropValue",
129                               self.emptyValueProperty(propertyId, areaId))
130        logging.info("0x%x get request: %s", propertyId, vp)
131        status, value = self.vehicle.get(vp)
132        logging.info("0x%x get response: %s, %s", propertyId, status, value)
133        if self.vtypes.StatusCode.OK == status:
134            return value
135        else:
136            logging.warning("attempt to read property 0x%x returned error %d",
137                            propertyId, status)
138
139    def setVhalProperty(self, propertyId, value, areaId=0, expectedStatus=0):
140        """Sets a specified property in the Vehicle HAL.
141
142        Args:
143            propertyId: the numeric identifier of the property to be set.
144            value: the value of the property, formatted as per the Vehicle HAL
145                   (use emptyValueProperty() as a helper).
146            areaId: the numeric identifier of the vehicle area to set the
147                    property for. 0, or omitted, for global.
148            expectedStatus: the StatusCode expected to be returned from setting
149                    the property. 0, or omitted, for OK.
150        """
151        propValue = self.emptyValueProperty(propertyId, areaId)
152        for k in propValue["value"]:
153            if k in value:
154                if k == "stringValue":
155                    propValue["value"][k] += value[k]
156                else:
157                    propValue["value"][k].extend(value[k])
158        vp = self.vtypes.Py2Pb("VehiclePropValue", propValue)
159        logging.info("0x%x set request: %s", propertyId, vp)
160        status = self.vehicle.set(vp)
161        logging.info("0x%x set response: %s", propertyId, status)
162        if 0 == expectedStatus:
163            expectedStatus = self.vtypes.StatusCode.OK
164        asserts.assertEqual(expectedStatus, status, "Prop 0x%x" % propertyId)
165
166    def setAndVerifyIntProperty(self, propertyId, value, areaId=0):
167        """Sets a integer property in the Vehicle HAL and reads it back.
168
169        Args:
170            propertyId: the numeric identifier of the property to be set.
171            value: the int32 value of the property to be set.
172            areaId: the numeric identifier of the vehicle area to set the
173                    property for. 0, or omitted, for global.
174        """
175        self.setVhalProperty(
176            propertyId, {"int32Values": [value]}, areaId=areaId)
177
178        propValue = self.readVhalProperty(propertyId, areaId=areaId)
179        asserts.assertEqual(1, len(propValue["value"]["int32Values"]))
180        asserts.assertEqual(value, propValue["value"]["int32Values"][0])
181
182    def extractZonesAsList(self, supportedAreas):
183        """Converts bitwise area flags to list of zones"""
184        allZones = [
185            self.vtypes.VehicleAreaZone.ROW_1_LEFT,
186            self.vtypes.VehicleAreaZone.ROW_1_CENTER,
187            self.vtypes.VehicleAreaZone.ROW_1_RIGHT,
188            self.vtypes.VehicleAreaZone.ROW_2_LEFT,
189            self.vtypes.VehicleAreaZone.ROW_2_CENTER,
190            self.vtypes.VehicleAreaZone.ROW_2_RIGHT,
191            self.vtypes.VehicleAreaZone.ROW_3_LEFT,
192            self.vtypes.VehicleAreaZone.ROW_3_CENTER,
193            self.vtypes.VehicleAreaZone.ROW_3_RIGHT,
194            self.vtypes.VehicleAreaZone.ROW_4_LEFT,
195            self.vtypes.VehicleAreaZone.ROW_4_CENTER,
196            self.vtypes.VehicleAreaZone.ROW_4_RIGHT,
197        ]
198
199        extractedZones = []
200        for zone in allZones:
201            if (zone & supportedAreas == zone):
202                extractedZones.append(zone)
203        return extractedZones
204
205    def disableTestHvacPowerOn(self):
206        # Disable this test for now.  HVAC Power On will no longer behave like this now that we've
207        #   added the status field in VehiclePropValue.  Need to update the test for this.
208        """Test power on/off and properties associated with it.
209
210        Gets the list of properties that are affected by the HVAC power state
211        and validates them.
212
213        Turns power on to start in a defined state, verifies that power is on
214        and properties are available.  State change from on->off and verifies
215        that properties are no longer available, then state change again from
216        off->on to verify properties are now available again.
217        """
218
219        # Checks that HVAC_POWER_ON property is supported and returns valid
220        # result initially.
221        hvacPowerOnConfig = self.propToConfig[
222            self.vtypes.VehicleProperty.HVAC_POWER_ON]
223        if hvacPowerOnConfig is None:
224            logging.info("HVAC_POWER_ON not supported")
225            return
226
227        zones = self.extractZonesAsList(hvacPowerOnConfig['supportedAreas'])
228        asserts.assertLess(
229            0, len(zones),
230            "supportedAreas for HVAC_POWER_ON property is invalid")
231
232        # TODO(pavelm): consider to check for all zones
233        zone = zones[0]
234
235        propValue = self.readVhalProperty(
236            self.vtypes.VehicleProperty.HVAC_POWER_ON, areaId=zone)
237
238        asserts.assertEqual(1, len(propValue["value"]["int32Values"]))
239        asserts.assertTrue(
240            propValue["value"]["int32Values"][0] in [0, 1],
241            "%d not a valid value for HVAC_POWER_ON" %
242            propValue["value"]["int32Values"][0])
243
244        # Checks that HVAC_POWER_ON config string returns valid result.
245        requestConfig = [
246            self.vtypes.Py2Pb("VehicleProperty",
247                              self.vtypes.VehicleProperty.HVAC_POWER_ON)
248        ]
249        logging.info("HVAC power on config request: %s", requestConfig)
250        responseConfig = self.vehicle.getPropConfigs(requestConfig)
251        logging.info("HVAC power on config response: %s", responseConfig)
252        hvacTypes = set([
253            self.vtypes.VehicleProperty.HVAC_FAN_SPEED,
254            self.vtypes.VehicleProperty.HVAC_FAN_DIRECTION,
255            self.vtypes.VehicleProperty.HVAC_TEMPERATURE_CURRENT,
256            self.vtypes.VehicleProperty.HVAC_TEMPERATURE_SET,
257            self.vtypes.VehicleProperty.HVAC_DEFROSTER,
258            self.vtypes.VehicleProperty.HVAC_AC_ON,
259            self.vtypes.VehicleProperty.HVAC_MAX_AC_ON,
260            self.vtypes.VehicleProperty.HVAC_MAX_DEFROST_ON,
261            self.vtypes.VehicleProperty.HVAC_RECIRC_ON,
262            self.vtypes.VehicleProperty.HVAC_DUAL_ON,
263            self.vtypes.VehicleProperty.HVAC_AUTO_ON,
264            self.vtypes.VehicleProperty.HVAC_ACTUAL_FAN_SPEED_RPM,
265        ])
266        status = responseConfig[0]
267        asserts.assertEqual(self.vtypes.StatusCode.OK, status)
268        configString = responseConfig[1][0]["configString"]
269        configProps = []
270        if configString != "":
271            for prop in configString.split(","):
272                configProps.append(int(prop, 16))
273        for prop in configProps:
274            asserts.assertTrue(prop in hvacTypes,
275                               "0x%X not an HVAC type" % prop)
276
277        # Turn power on.
278        self.setAndVerifyIntProperty(
279            self.vtypes.VehicleProperty.HVAC_POWER_ON, 1, areaId=zone)
280
281        # Check that properties that require power to be on can be set.
282        propVals = {}
283        for prop in configProps:
284            v = self.readVhalProperty(prop, areaId=zone)["value"]
285            self.setVhalProperty(prop, v, areaId=zone)
286            # Save the value for use later when trying to set the property when
287            # HVAC is off.
288            propVals[prop] = v
289
290        # Turn power off.
291        self.setAndVerifyIntProperty(
292            self.vtypes.VehicleProperty.HVAC_POWER_ON, 0, areaId=zone)
293
294        # Check that properties that require power to be on can't be set.
295        for prop in configProps:
296            self.setVhalProperty(
297                prop,
298                propVals[prop],
299                areaId=zone,
300                expectedStatus=self.vtypes.StatusCode.NOT_AVAILABLE)
301
302        # Turn power on.
303        self.setAndVerifyIntProperty(
304            self.vtypes.VehicleProperty.HVAC_POWER_ON, 1, areaId=zone)
305
306        # Check that properties that require power to be on can be set.
307        for prop in configProps:
308            self.setVhalProperty(prop, propVals[prop], areaId=zone)
309
310    def testSetBoolPropResponseTime(self):
311        """Verifies that a PropertyEvent arrives in a reasonable time on Boolean Properties"""
312
313        # PropertyEvent is received
314        self._arrived = False
315
316        def onPropertyEvent(vehiclePropValues):
317            for vp in vehiclePropValues:
318                if vp["prop"] & self.vtypes.VehiclePropertyType.BOOLEAN != 0:
319                    logging.info("onPropertyEvent received: %s",
320                                 vehiclePropValues)
321                    self._arrived = True
322
323        def onPropertySetError(errorCode, propId, areaId):
324            logging.info(
325                "onPropertySetError, error: %d, prop: 0x%x, area: 0x%x",
326                errorCode, propId, areaId)
327            self._arrived = True
328
329        for c in self.configList:
330            if (c["access"] != self.vtypes.VehiclePropertyAccess.READ_WRITE or
331            c["changeMode"] != self.vtypes.VehiclePropertyChangeMode.ON_CHANGE or
332            c["prop"] & self.vtypes.VehiclePropertyType.MASK
333                != self.vtypes.VehiclePropertyType.BOOLEAN):
334                continue
335
336            self._arrived = False
337
338            # Register for on_change property
339            prop = c["prop"]
340            callback = self.vehicle.GetHidlCallbackInterface(
341                "IVehicleCallback",
342                onPropertyEvent=onPropertyEvent,
343                onPropertySetError=onPropertySetError
344            )
345            subscribeOption = {
346                "propId": prop,
347                "sampleRate": 0.0,  # ON_CHANGE
348                "flags": self.vtypes.SubscribeFlags.EVENTS_FROM_CAR,
349            }
350            pbSubscribeOption = self.vtypes.Py2Pb("SubscribeOptions",
351                                                  subscribeOption)
352            statusCode = self.vehicle.subscribe(callback, [pbSubscribeOption])
353            asserts.assertEqual(statusCode, 0,
354                "Must successfully subscribe to property 0x%x" % prop)
355
356            # Change value of properties
357            for area in c["areaConfigs"]:
358                currPropVal = self.readVhalProperty(prop, area["areaId"])
359                updateVal = [0]
360                if (currPropVal["value"]["int32Values"] is None or
361                    currPropVal["value"]["int32Values"] == [0]):
362                    updateVal = [1]
363                propValue = self.emptyValueProperty(prop, area["areaId"])
364                for index in propValue["value"]:
365                    if index == "int32Values":
366                        propValue["value"][index].extend(updateVal)
367                vp = self.vtypes.Py2Pb("VehiclePropValue", propValue)
368                status = self.vehicle.set(vp)
369                if status != 0:
370                    logging.warning("Set value failed for Property 0x%x" % prop)
371                    continue
372
373                # Check callback is received in 5 second
374                waitingTime = 5
375                checkTimes = 5
376                for _ in xrange(checkTimes):
377                    if self._arrived:
378                        logging.info(
379                            "callback for Property: 0x%x is received" %
380                            prop)
381                        break
382                    time.sleep(waitingTime/checkTimes)
383
384                if not self._arrived:
385                    asserts.fail(
386                        "callback is not received in 5 seconds for Property: 0x%x"
387                        % prop)
388                self.vehicle.unsubscribe(callback, prop)
389
390    def testVehicleStaticProps(self):
391        """Verifies that static properties are configured correctly"""
392        staticProperties = set([
393            self.vtypes.VehicleProperty.INFO_VIN,
394            self.vtypes.VehicleProperty.INFO_MAKE,
395            self.vtypes.VehicleProperty.INFO_MODEL,
396            self.vtypes.VehicleProperty.INFO_MODEL_YEAR,
397            self.vtypes.VehicleProperty.INFO_FUEL_CAPACITY,
398            self.vtypes.VehicleProperty.INFO_FUEL_TYPE,
399            self.vtypes.VehicleProperty.INFO_EV_BATTERY_CAPACITY,
400            self.vtypes.VehicleProperty.INFO_EV_CONNECTOR_TYPE,
401            self.vtypes.VehicleProperty.HVAC_FAN_DIRECTION_AVAILABLE,
402            self.vtypes.VehicleProperty.AP_POWER_BOOTUP_REASON,
403            self.vtypes.VehicleProperty.INFO_FUEL_DOOR_LOCATION,
404            self.vtypes.VehicleProperty.INFO_EV_PORT_LOCATION,
405            self.vtypes.VehicleProperty.INFO_DRIVER_SEAT,
406        ])
407        for c in self.configList:
408            prop = c['prop']
409            msg = "Prop 0x%x" % prop
410            if (c["prop"] in staticProperties):
411                asserts.assertEqual(
412                    self.vtypes.VehiclePropertyChangeMode.STATIC,
413                    c["changeMode"], msg)
414                asserts.assertEqual(self.vtypes.VehiclePropertyAccess.READ,
415                                    c["access"], msg)
416                for area in c["areaConfigs"]:
417                    propValue = self.readVhalProperty(prop, area["areaId"])
418                    asserts.assertEqual(prop, propValue["prop"])
419                    self.setVhalProperty(
420                        prop,
421                        propValue["value"],
422                        expectedStatus=self.vtypes.StatusCode.ACCESS_DENIED)
423            else:  # Non-static property
424                asserts.assertNotEqual(
425                    self.vtypes.VehiclePropertyChangeMode.STATIC,
426                    c["changeMode"], msg)
427
428    def testPropertyRanges(self):
429        """Retrieve the property ranges for all areas.
430
431        This checks that the areas noted in the config all give valid area
432        configs.  Once these are validated, the values for all these areas
433        retrieved from the HIDL must be within the ranges defined."""
434
435        enumProperties = {
436            self.vtypes.VehicleProperty.ENGINE_OIL_LEVEL,
437            self.vtypes.VehicleProperty.GEAR_SELECTION,
438            self.vtypes.VehicleProperty.CURRENT_GEAR,
439            self.vtypes.VehicleProperty.TURN_SIGNAL_STATE,
440            self.vtypes.VehicleProperty.IGNITION_STATE,
441            self.vtypes.VehicleProperty.HVAC_FAN_DIRECTION,
442            self.vtypes.VehicleProperty.HVAC_FAN_DIRECTION_AVAILABLE,
443            self.vtypes.VehicleProperty.HAZARD_LIGHTS_STATE,
444            self.vtypes.VehicleProperty.FOG_LIGHTS_STATE,
445            self.vtypes.VehicleProperty.HEADLIGHTS_STATE,
446            self.vtypes.VehicleProperty.HIGH_BEAM_LIGHTS_STATE,
447            self.vtypes.VehicleProperty.HEADLIGHTS_SWITCH,
448            self.vtypes.VehicleProperty.HIGH_BEAM_LIGHTS_SWITCH,
449            self.vtypes.VehicleProperty.FOG_LIGHTS_SWITCH,
450            self.vtypes.VehicleProperty.HAZARD_LIGHTS_SWITCH,
451            self.vtypes.VehicleProperty.INFO_EV_PORT_LOCATION,
452            self.vtypes.VehicleProperty.INFO_FUEL_DOOR_LOCATION,
453            self.vtypes.VehicleProperty.INFO_DRIVER_SEAT,
454        }
455
456        for c in self.configList:
457            # Continuous properties need to have a sampling frequency.
458            if c["changeMode"] == self.vtypes.VehiclePropertyChangeMode.CONTINUOUS:
459                asserts.assertTrue(
460                     c["minSampleRate"] >= 0.0 ,
461                    "minSampleRate should be >= 0. Config list: %s" % c)
462                asserts.assertLess(
463                    0.0, c["maxSampleRate"],
464                    "maxSampleRate should be > 0. Config list: %s" % c)
465                asserts.assertFalse(
466                    c["minSampleRate"] > c["maxSampleRate"],
467                    "Prop 0x%x minSampleRate > maxSampleRate" % c["prop"])
468
469            if c["prop"] & self.vtypes.VehiclePropertyType.BOOLEAN != 0:
470                # Boolean types don't have ranges
471                continue
472
473            if (c["access"] != self.vtypes.VehiclePropertyAccess.READ_WRITE and
474                c["access"] != self.vtypes.VehiclePropertyAccess.READ):
475                # Skip the test if properties are not readable.
476                continue
477
478            if c["prop"] in enumProperties:
479                # This property does not use traditional min/max ranges
480                continue
481
482            asserts.assertTrue(c["areaConfigs"] != None,
483                               "Prop 0x%x must have areaConfigs" % c["prop"])
484            areasFound = 0
485            if c["prop"] == self.vtypes.VehicleProperty.HVAC_TEMPERATURE_DISPLAY_UNITS:
486                # This property doesn't have sensible min/max
487                continue
488
489            for a in c["areaConfigs"]:
490                # Make sure this doesn't override one of the other areas found.
491                asserts.assertEqual(0, areasFound & a["areaId"])
492                areasFound |= a["areaId"]
493
494                # Do some basic checking the min and max aren't mixed up.
495                checks = [("minInt32Value", "maxInt32Value"),
496                          ("minInt64Value", "maxInt64Value"),
497                          ("minFloatValue", "maxFloatValue")]
498                for minName, maxName in checks:
499                    asserts.assertFalse(
500                        a[minName] > a[maxName],
501                        "Prop 0x%x Area 0x%X %s > %s: %d > %d" %
502                        (c["prop"], a["areaId"], minName, maxName, a[minName],
503                         a[maxName]))
504
505                # Get a value and make sure it's within the bounds.
506                propVal = self.readVhalProperty(c["prop"], a["areaId"])
507                # Some values may not be available, which is not an error.
508                if propVal is None:
509                    continue
510                val = propVal["value"]
511                valTypes = {
512                    "int32Values": ("minInt32Value", "maxInt32Value"),
513                    "int64Values": ("minInt64Value", "maxInt64Value"),
514                    "floatValues": ("minFloatValue", "maxFloatValue"),
515                }
516                for valType, valBoundNames in valTypes.items():
517                    for v in val[valType]:
518                        # Make sure value isn't less than the minimum.
519                        asserts.assertFalse(
520                            v < a[valBoundNames[0]],
521                            "Prop 0x%x Area 0x%X %s < min: %s < %s" %
522                            (c["prop"], a["areaId"], valType, v,
523                             a[valBoundNames[0]]))
524                        # Make sure value isn't greater than the maximum.
525                        asserts.assertFalse(
526                            v > a[valBoundNames[1]],
527                            "Prop 0x%x Area 0x%X %s > max: %s > %s" %
528                            (c["prop"], a["areaId"], valType, v,
529                             a[valBoundNames[1]]))
530
531    def getValueIfPropSupported(self, propertyId):
532        """Returns tuple of boolean (indicating value supported or not) and the value itself"""
533        if (propertyId in self.propToConfig):
534            propValue = self.readVhalProperty(propertyId)
535            asserts.assertNotEqual(None, propValue,
536                                   "expected value, prop: 0x%x" % propertyId)
537            asserts.assertEqual(propertyId, propValue['prop'])
538            return True, self.extractValue(propValue)
539        else:
540            return False, None
541
542    def testInfoVinMakeModel(self):
543        """Verifies INFO_VIN, INFO_MAKE, INFO_MODEL properties"""
544        stringProperties = set([
545            self.vtypes.VehicleProperty.INFO_VIN,
546            self.vtypes.VehicleProperty.INFO_MAKE,
547            self.vtypes.VehicleProperty.INFO_MODEL
548        ])
549        for prop in stringProperties:
550            supported, val = self.getValueIfPropSupported(prop)
551            if supported:
552                asserts.assertEqual(str, type(val), "prop: 0x%x" % prop)
553                asserts.assertTrue(0 <= (len(val)), "prop: 0x%x" % prop)
554
555    def testGlobalFloatProperties(self):
556        """Verifies that values of global float properties are in the correct range"""
557        floatProperties = {
558            self.vtypes.VehicleProperty.ENV_OUTSIDE_TEMPERATURE: (-50, 100),  # celsius
559            self.vtypes.VehicleProperty.ENGINE_RPM : (0, 30000),  # RPMs
560            self.vtypes.VehicleProperty.ENGINE_OIL_TEMP : (-50, 150),  # celsius
561            self.vtypes.VehicleProperty.ENGINE_COOLANT_TEMP : (-50, 150),  #
562            self.vtypes.VehicleProperty.PERF_VEHICLE_SPEED : (0, 150),  # m/s, 150 m/s = 330 mph
563            self.vtypes.VehicleProperty.PERF_VEHICLE_SPEED_DISPLAY : (0, 150),  # 150 m/s = 330 mph
564            self.vtypes.VehicleProperty.PERF_STEERING_ANGLE : (-180, 180),  # degrees
565            self.vtypes.VehicleProperty.PERF_ODOMETER : (0, 1000000),  # km
566            self.vtypes.VehicleProperty.INFO_FUEL_CAPACITY : (0, 1000000),  # milliliter
567        }
568
569        for prop, validRange in floatProperties.iteritems():
570            supported, val = self.getValueIfPropSupported(prop)
571            if supported:
572                asserts.assertEqual(float, type(val))
573                self.assertValueInRangeForProp(val, validRange[0],
574                                               validRange[1], prop)
575
576    def testGlobalBoolProperties(self):
577        """Verifies that values of global boolean properties are in the correct range"""
578        booleanProperties = set([
579            self.vtypes.VehicleProperty.PARKING_BRAKE_ON,
580            self.vtypes.VehicleProperty.FUEL_LEVEL_LOW,
581            self.vtypes.VehicleProperty.NIGHT_MODE,
582            self.vtypes.VehicleProperty.ABS_ACTIVE,
583            self.vtypes.VehicleProperty.FUEL_DOOR_OPEN,
584            self.vtypes.VehicleProperty.EV_CHARGE_PORT_OPEN,
585            self.vtypes.VehicleProperty.EV_CHARGE_PORT_CONNECTED,
586        ])
587        for prop in booleanProperties:
588            self.verifyEnumPropIfSupported(prop, [0, 1])
589
590    def testGlobalEnumProperties(self):
591        """Verifies that values of global enum properties are in the correct range"""
592        enumProperties = {
593            self.vtypes.VehicleProperty.ENGINE_OIL_LEVEL:
594            self.vtypes.VehicleOilLevel,
595            self.vtypes.VehicleProperty.GEAR_SELECTION:
596            self.vtypes.VehicleGear,
597            self.vtypes.VehicleProperty.CURRENT_GEAR:
598            self.vtypes.VehicleGear,
599            self.vtypes.VehicleProperty.TURN_SIGNAL_STATE:
600            self.vtypes.VehicleTurnSignal,
601            self.vtypes.VehicleProperty.IGNITION_STATE:
602            self.vtypes.VehicleIgnitionState,
603        }
604        for prop, enum in enumProperties.iteritems():
605            self.verifyEnumPropIfSupported(prop, vars(enum).values())
606
607    def testDebugDump(self):
608        """Verifies that call to IVehicle#debugDump is not failing"""
609        dumpStr = self.vehicle.debugDump()
610        asserts.assertNotEqual(None, dumpStr)
611
612    def extractValue(self, propValue):
613        """Extracts value depending on data type of the property"""
614        if propValue == None:
615            return None
616
617        # Extract data type
618        dataType = propValue['prop'] & self.vtypes.VehiclePropertyType.MASK
619        val = propValue['value']
620        if self.vtypes.VehiclePropertyType.STRING == dataType:
621            asserts.assertNotEqual(None, val['stringValue'])
622            return val['stringValue']
623        elif self.vtypes.VehiclePropertyType.INT32 == dataType or \
624                self.vtypes.VehiclePropertyType.BOOLEAN == dataType:
625            asserts.assertEqual(1, len(val["int32Values"]))
626            return val["int32Values"][0]
627        elif self.vtypes.VehiclePropertyType.INT64 == dataType:
628            asserts.assertEqual(1, len(val["int64Values"]))
629            return val["int64Values"][0]
630        elif self.vtypes.VehiclePropertyType.FLOAT == dataType:
631            asserts.assertEqual(1, len(val["floatValues"]))
632            return val["floatValues"][0]
633        elif self.vtypes.VehiclePropertyType.INT32_VEC == dataType:
634            asserts.assertLess(0, len(val["int32Values"]))
635            return val["int32Values"]
636        elif self.vtypes.VehiclePropertyType.FLOAT_VEC == dataType:
637            asserts.assertLess(0, len(val["floatValues"]))
638            return val["floatValues"]
639        elif self.vtypes.VehiclePropertyType.BYTES == dataType:
640            asserts.assertLess(0, len(val["bytes"]))
641            return val["bytes"]
642        else:
643            return val
644
645    def verifyEnumPropIfSupported(self, propertyId, validValues):
646        """Verifies that if given property supported it is one of the value in validValues set"""
647        supported, val = self.getValueIfPropSupported(propertyId)
648        if supported:
649            asserts.assertEqual(int, type(val))
650            self.assertIntValueInRangeForProp(val, validValues, propertyId)
651
652    def assertLessOrEqual(self, first, second, msg=None):
653        """Asserts that first <= second"""
654        if second < first:
655            fullMsg = "%s is not less or equal to %s" % (first, second)
656            if msg:
657                fullMsg = "%s %s" % (fullMsg, msg)
658            fail(fullMsg)
659
660    def assertIntValueInRangeForProp(self, value, validValues, prop):
661        """Asserts that given value is in the validValues range"""
662        asserts.assertTrue(
663            value in validValues,
664            "Invalid value %d for property: 0x%x, expected one of: %s" %
665            (value, prop, validValues))
666
667    def assertValueInRangeForProp(self, value, rangeBegin, rangeEnd, prop):
668        """Asserts that given value is in the range [rangeBegin, rangeEnd]"""
669        msg = "Value %s is out of range [%s, %s] for property 0x%x" % (
670            value, rangeBegin, rangeEnd, prop)
671        self.assertLessOrEqual(rangeBegin, value, msg)
672        self.assertLessOrEqual(value, rangeEnd, msg)
673
674    def getPropConfig(self, propertyId):
675        return self.propToConfig.get(propertyId)
676
677
678    def isPropSupported(self, propertyId):
679        return self.getPropConfig(propertyId) is not None
680
681    def testEngineOilTemp(self):
682        """tests engine oil temperature.
683
684        This also tests an HIDL async callback.
685        """
686        self.onPropertyEventCalled = 0
687        self.onPropertySetErrorCalled = 0
688
689        def onPropertyEvent(vehiclePropValues):
690            logging.info("onPropertyEvent received: %s", vehiclePropValues)
691            self.onPropertyEventCalled += 1
692
693        def onPropertySetError(erroCode, propId, areaId):
694            logging.info(
695                "onPropertySetError, error: %d, prop: 0x%x, area: 0x%x",
696                erroCode, prop, area)
697            self.onPropertySetErrorCalled += 1
698
699        config = self.getPropConfig(
700            self.vtypes.VehicleProperty.ENGINE_OIL_TEMP)
701        if (config is None):
702            logging.info("ENGINE_OIL_TEMP property is not supported")
703            return  # Property not supported, we are done here.
704
705        propValue = self.readVhalProperty(
706            self.vtypes.VehicleProperty.ENGINE_OIL_TEMP)
707        asserts.assertEqual(1, len(propValue['value']['floatValues']))
708        oilTemp = propValue['value']['floatValues'][0]
709        logging.info("Current oil temperature: %f C", oilTemp)
710        asserts.assertLess(oilTemp, 200)  # Check it is in reasinable range
711        asserts.assertLess(-50, oilTemp)
712
713        if (config["changeMode"] ==
714                self.vtypes.VehiclePropertyChangeMode.CONTINUOUS):
715            logging.info(
716                "ENGINE_OIL_TEMP is continuous property, subscribing...")
717            callback = self.vehicle.GetHidlCallbackInterface(
718                "IVehicleCallback",
719                onPropertyEvent=onPropertyEvent,
720                onPropertySetError=onPropertySetError)
721
722            subscribeOptions = {
723                "propId": self.vtypes.VehicleProperty.ENGINE_OIL_TEMP,
724                "sampleRate": 1.0,  # Hz
725                "flags": self.vtypes.SubscribeFlags.EVENTS_FROM_CAR,
726            }
727            pbSubscribeOptions = self.vtypes.Py2Pb("SubscribeOptions",
728                                                   subscribeOptions)
729
730            statusCode = self.vehicle.subscribe(callback, [pbSubscribeOptions])
731            if statusCode != 0:
732                asserts.fail("Can not register ENGINE_OIL_TEMP")
733
734            for _ in range(5):
735                if (self.onPropertyEventCalled > 0
736                        or self.onPropertySetErrorCalled > 0):
737                    self.vehicle.unsubscribe(
738                        callback, self.vtypes.VehicleProperty.ENGINE_OIL_TEMP)
739                    return
740                time.sleep(1)
741            asserts.fail("Callback not called in 5 seconds.")
742
743    def getDiagnosticSupportInfo(self):
744        """Check which of the OBD2 diagnostic properties are supported."""
745        properties = [
746            self.vtypes.VehicleProperty.OBD2_LIVE_FRAME,
747            self.vtypes.VehicleProperty.OBD2_FREEZE_FRAME,
748            self.vtypes.VehicleProperty.OBD2_FREEZE_FRAME_INFO,
749            self.vtypes.VehicleProperty.OBD2_FREEZE_FRAME_CLEAR
750        ]
751        return {x: self.isPropSupported(x) for x in properties}
752
753    class CheckRead(object):
754        """An object whose job it is to read a Vehicle HAL property and run
755           routine validation checks on the result."""
756
757        def __init__(self, test, propertyId, areaId=0):
758            """Creates a CheckRead instance.
759
760            Args:
761                test: the containing testcase object.
762                propertyId: the numeric identifier of the vehicle property.
763            """
764            self.test = test
765            self.propertyId = propertyId
766            self.areaId = 0
767
768        def validateGet(self, status, value):
769            """Validate the result of IVehicle.get.
770
771            Args:
772                status: the StatusCode returned from Vehicle HAL.
773                value: the VehiclePropValue returned from Vehicle HAL.
774
775            Returns: a VehiclePropValue instance, or None on failure."""
776            asserts.assertEqual(self.test.vtypes.StatusCode.OK, status)
777            asserts.assertNotEqual(value, None)
778            asserts.assertEqual(self.propertyId, value['prop'])
779            return value
780
781        def prepareRequest(self, propValue):
782            """Setup this request with any property-specific data.
783
784            Args:
785                propValue: a dictionary in the format of a VehiclePropValue.
786
787            Returns: a dictionary in the format of a VehclePropValue."""
788            return propValue
789
790        def __call__(self):
791            asserts.assertTrue(
792                self.test.isPropSupported(self.propertyId), "error")
793            request = {
794                'prop': self.propertyId,
795                'timestamp': 0,
796                'areaId': self.areaId,
797                'status': self.test.vtypes.VehiclePropertyStatus.AVAILABLE,
798                'value': {
799                    'int32Values': [],
800                    'floatValues': [],
801                    'int64Values': [],
802                    'bytes': [],
803                    'stringValue': ""
804                }
805            }
806            request = self.prepareRequest(request)
807            requestPropValue = self.test.vtypes.Py2Pb("VehiclePropValue",
808                                                      request)
809            status, responsePropValue = self.test.vehicle.get(requestPropValue)
810            return self.validateGet(status, responsePropValue)
811
812    class CheckWrite(object):
813        """An object whose job it is to write a Vehicle HAL property and run
814           routine validation checks on the result."""
815
816        def __init__(self, test, propertyId, areaId=0):
817            """Creates a CheckWrite instance.
818
819            Args:
820                test: the containing testcase object.
821                propertyId: the numeric identifier of the vehicle property.
822                areaId: the numeric identifier of the vehicle area.
823            """
824            self.test = test
825            self.propertyId = propertyId
826            self.areaId = 0
827
828        def validateSet(self, status):
829            """Validate the result of IVehicle.set.
830            Reading back the written-to property to ensure a consistent
831            value is fair game for this method.
832
833            Args:
834                status: the StatusCode returned from Vehicle HAL.
835
836            Returns: None."""
837            asserts.assertEqual(self.test.vtypes.StatusCode.OK, status)
838
839        def prepareRequest(self, propValue):
840            """Setup this request with any property-specific data.
841
842            Args:
843                propValue: a dictionary in the format of a VehiclePropValue.
844
845            Returns: a dictionary in the format of a VehclePropValue."""
846            return propValue
847
848        def __call__(self):
849            asserts.assertTrue(
850                self.test.isPropSupported(self.propertyId), "error")
851            request = {
852                'prop': self.propertyId,
853                'timestamp': 0,
854                'areaId': self.areaId,
855                'status': self.test.vtypes.VehiclePropertyStatus.AVAILABLE,
856                'value': {
857                    'int32Values': [],
858                    'floatValues': [],
859                    'int64Values': [],
860                    'bytes': [],
861                    'stringValue': ""
862                }
863            }
864            request = self.prepareRequest(request)
865            requestPropValue = self.test.vtypes.Py2Pb("VehiclePropValue",
866                                                      request)
867            status = self.test.vehicle.set(requestPropValue)
868            return self.validateSet(status)
869
870    def testReadObd2LiveFrame(self):
871        """Test that one can correctly read the OBD2 live frame."""
872        supportInfo = self.getDiagnosticSupportInfo()
873        if supportInfo[self.vtypes.VehicleProperty.OBD2_LIVE_FRAME]:
874            checkRead = self.CheckRead(
875                self, self.vtypes.VehicleProperty.OBD2_LIVE_FRAME)
876            checkRead()
877        else:
878            # live frame not supported by this HAL implementation. done
879            logging.info("OBD2_LIVE_FRAME not supported.")
880
881    def testReadObd2FreezeFrameInfo(self):
882        """Test that one can read the list of OBD2 freeze timestamps."""
883        supportInfo = self.getDiagnosticSupportInfo()
884        if supportInfo[self.vtypes.VehicleProperty.OBD2_FREEZE_FRAME_INFO]:
885            checkRead = self.CheckRead(
886                self, self.vtypes.VehicleProperty.OBD2_FREEZE_FRAME_INFO)
887            checkRead()
888        else:
889            # freeze frame info not supported by this HAL implementation. done
890            logging.info("OBD2_FREEZE_FRAME_INFO not supported.")
891
892    def testReadValidObd2FreezeFrame(self):
893        """Test that one can read the OBD2 freeze frame data."""
894
895        class FreezeFrameCheckRead(self.CheckRead):
896            def __init__(self, test, timestamp):
897                self.test = test
898                self.propertyId = \
899                    self.test.vtypes.VehicleProperty.OBD2_FREEZE_FRAME
900                self.timestamp = timestamp
901                self.areaId = 0
902
903            def prepareRequest(self, propValue):
904                propValue['value']['int64Values'] = [self.timestamp]
905                return propValue
906
907            def validateGet(self, status, value):
908                # None is acceptable, as a newer fault could have overwritten
909                # the one we're trying to read
910                if value is not None:
911                    asserts.assertEqual(self.test.vtypes.StatusCode.OK, status)
912                    asserts.assertEqual(self.propertyId, value['prop'])
913                    asserts.assertEqual(self.timestamp, value['timestamp'])
914
915        supportInfo = self.getDiagnosticSupportInfo()
916        if supportInfo[self.vtypes.VehicleProperty.OBD2_FREEZE_FRAME_INFO] \
917            and supportInfo[self.vtypes.VehicleProperty.OBD2_FREEZE_FRAME]:
918            infoCheckRead = self.CheckRead(
919                self, self.vtypes.VehicleProperty.OBD2_FREEZE_FRAME_INFO)
920            frameInfos = infoCheckRead()
921            timestamps = frameInfos["value"]["int64Values"]
922            for timestamp in timestamps:
923                freezeCheckRead = FreezeFrameCheckRead(self, timestamp)
924                freezeCheckRead()
925        else:
926            # freeze frame not supported by this HAL implementation. done
927            logging.info("OBD2_FREEZE_FRAME and _INFO not supported.")
928
929    def testReadInvalidObd2FreezeFrame(self):
930        """Test that trying to read freeze frame at invalid timestamps
931            behaves correctly (i.e. returns an error code)."""
932
933        class FreezeFrameCheckRead(self.CheckRead):
934            def __init__(self, test, timestamp):
935                self.test = test
936                self.propertyId = self.test.vtypes.VehicleProperty.OBD2_FREEZE_FRAME
937                self.timestamp = timestamp
938                self.areaId = 0
939
940            def prepareRequest(self, propValue):
941                propValue['value']['int64Values'] = [self.timestamp]
942                return propValue
943
944            def validateGet(self, status, value):
945                asserts.assertEqual(self.test.vtypes.StatusCode.INVALID_ARG,
946                                    status)
947
948        supportInfo = self.getDiagnosticSupportInfo()
949        invalidTimestamps = [0, 482005800]
950        if supportInfo[self.vtypes.VehicleProperty.OBD2_FREEZE_FRAME]:
951            for timestamp in invalidTimestamps:
952                freezeCheckRead = FreezeFrameCheckRead(self, timestamp)
953                freezeCheckRead()
954        else:
955            # freeze frame not supported by this HAL implementation. done
956            logging.info("OBD2_FREEZE_FRAME not supported.")
957
958    def testClearValidObd2FreezeFrame(self):
959        """Test that deleting a diagnostic freeze frame works.
960        Given the timing behavor of OBD2_FREEZE_FRAME, the only sensible
961        definition of works here is that, after deleting a frame, trying to read
962        at its timestamp, will not be successful."""
963
964        class FreezeFrameClearCheckWrite(self.CheckWrite):
965            def __init__(self, test, timestamp):
966                self.test = test
967                self.propertyId = self.test.vtypes.VehicleProperty.OBD2_FREEZE_FRAME_CLEAR
968                self.timestamp = timestamp
969                self.areaId = 0
970
971            def prepareRequest(self, propValue):
972                propValue['value']['int64Values'] = [self.timestamp]
973                return propValue
974
975            def validateSet(self, status):
976                asserts.assertTrue(
977                    status in [
978                        self.test.vtypes.StatusCode.OK,
979                        self.test.vtypes.StatusCode.INVALID_ARG
980                    ], "error")
981
982        class FreezeFrameCheckRead(self.CheckRead):
983            def __init__(self, test, timestamp):
984                self.test = test
985                self.propertyId = \
986                    self.test.vtypes.VehicleProperty.OBD2_FREEZE_FRAME
987                self.timestamp = timestamp
988                self.areaId = 0
989
990            def prepareRequest(self, propValue):
991                propValue['value']['int64Values'] = [self.timestamp]
992                return propValue
993
994            def validateGet(self, status, value):
995                asserts.assertEqual(self.test.vtypes.StatusCode.INVALID_ARG,
996                                    status)
997
998        supportInfo = self.getDiagnosticSupportInfo()
999        if supportInfo[self.vtypes.VehicleProperty.OBD2_FREEZE_FRAME_INFO] \
1000            and supportInfo[self.vtypes.VehicleProperty.OBD2_FREEZE_FRAME] \
1001            and supportInfo[self.vtypes.VehicleProperty.OBD2_FREEZE_FRAME_CLEAR]:
1002            infoCheckRead = self.CheckRead(
1003                self, self.vtypes.VehicleProperty.OBD2_FREEZE_FRAME_INFO)
1004            frameInfos = infoCheckRead()
1005            timestamps = frameInfos["value"]["int64Values"]
1006            for timestamp in timestamps:
1007                checkWrite = FreezeFrameClearCheckWrite(self, timestamp)
1008                checkWrite()
1009                checkRead = FreezeFrameCheckRead(self, timestamp)
1010                checkRead()
1011        else:
1012            # freeze frame not supported by this HAL implementation. done
1013            logging.info("OBD2_FREEZE_FRAME, _CLEAR and _INFO not supported.")
1014
1015    def testClearInvalidObd2FreezeFrame(self):
1016        """Test that deleting an invalid freeze frame behaves correctly."""
1017
1018        class FreezeFrameClearCheckWrite(self.CheckWrite):
1019            def __init__(self, test, timestamp):
1020                self.test = test
1021                self.propertyId = \
1022                    self.test.vtypes.VehicleProperty.OBD2_FREEZE_FRAME_CLEAR
1023                self.timestamp = timestamp
1024                self.areaId = 0
1025
1026            def prepareRequest(self, propValue):
1027                propValue['value']['int64Values'] = [self.timestamp]
1028                return propValue
1029
1030            def validateSet(self, status):
1031                asserts.assertEqual(
1032                    self.test.vtypes.StatusCode.INVALID_ARG, status,
1033                    "PropId: 0x%s, Timestamp: %d" % (self.propertyId,
1034                                                     self.timestamp))
1035
1036        supportInfo = self.getDiagnosticSupportInfo()
1037        if supportInfo[self.vtypes.VehicleProperty.OBD2_FREEZE_FRAME_CLEAR]:
1038            invalidTimestamps = [0, 482005800]
1039            for timestamp in invalidTimestamps:
1040                checkWrite = FreezeFrameClearCheckWrite(self, timestamp)
1041                checkWrite()
1042        else:
1043            # freeze frame not supported by this HAL implementation. done
1044            logging.info("OBD2_FREEZE_FRAME_CLEAR not supported.")
1045
1046
1047if __name__ == "__main__":
1048    test_runner.main()
1049