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