Home | History | Annotate | Download | only in emulator
      1 #!/usr/bin/env python
      2 # -*- coding: utf-8 -*-
      3 #
      4 # Copyright 2016 Google Inc.
      5 #
      6 # Licensed under the Apache License, Version 2.0 (the "License");
      7 # you may not use this file except in compliance with the License.
      8 # You may obtain a copy of the License at
      9 #
     10 #   http://www.apache.org/licenses/LICENSE-2.0
     11 #
     12 # Unless required by applicable law or agreed to in writing, software
     13 # distributed under the License is distributed on an "AS IS" BASIS,
     14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15 # See the License for the specific language governing permissions and
     16 # limitations under the License.
     17 #
     18 
     19 """
     20     This module provides a vhal class which sends and receives messages to the vehicle HAL module
     21     on an Android Auto device.  It uses port forwarding via ADB to communicate with the Android
     22     device.
     23 
     24     Example Usage:
     25 
     26         import vhal_consts_2_0 as c
     27         from vhal_emulator import Vhal
     28 
     29         # Create an instance of vhal class.  Need to pass the vhal_types constants.
     30         v = Vhal(c.vhal_types_2_0)
     31 
     32         # Get the property config (if desired)
     33         v.getConfig(c.VEHICLEPROPERTY_HVAC_TEMPERATURE_SET)
     34 
     35         # Get the response message to getConfig()
     36         reply = v.rxMsg()
     37         print(reply)
     38 
     39         # Set left temperature to 70 degrees
     40         v.setProperty(c.VEHICLEPROPERTY_HVAC_TEMPERATURE_SET, c.VEHICLEAREAZONE_ROW_1_LEFT, 70)
     41 
     42         # Get the response message to setProperty()
     43         reply = v.rxMsg()
     44         print(reply)
     45 
     46         # Get the left temperature value
     47         v.getProperty(c.VEHICLEPROPERTY_HVAC_TEMPERATURE_SET, c.VEHICLEAREAZONE_ROW_1_LEFT)
     48 
     49         # Get the response message to getProperty()
     50         reply = v.rxMsg()
     51         print(reply)
     52 
     53     NOTE:  The rxMsg() is a blocking call, so it may be desirable to set up a separate RX thread
     54             to handle any asynchronous messages coming from the device.
     55 
     56     Example for creating RX thread (assumes vhal has already been instantiated):
     57 
     58         from threading import Thread
     59 
     60         # Define a simple thread that receives messages from a vhal object (v) and prints them
     61         def rxThread(v):
     62             while(1):
     63                 print v.rxMsg()
     64 
     65         rx = Thread(target=rxThread, args=(v,))
     66         rx.start()
     67 
     68     Protocol Buffer:
     69         This module relies on VehicleHalProto_pb2.py being in sync with the protobuf in the VHAL.
     70         If the VehicleHalProto.proto file has changed, re-generate the python version using:
     71 
     72             protoc -I=<proto_dir> --python_out=<out_dir> <proto_dir>/VehicleHalProto.proto
     73 """
     74 
     75 from __future__ import print_function
     76 
     77 # Suppress .pyc files
     78 import sys
     79 sys.dont_write_bytecode = True
     80 
     81 import socket
     82 import struct
     83 import subprocess
     84 
     85 # Generate the protobuf file from hardware/interfaces/automotive/vehicle/2.0/default/impl/vhal_v2_0
     86 # It is recommended to use the protoc provided in: prebuilts/tools/common/m2/repository/com/google/protobuf/protoc/3.0.0
     87 # or a later version, in order to provide Python 3 compatibility
     88 #   protoc -I=proto --python_out=proto proto/VehicleHalProto.proto
     89 import VehicleHalProto_pb2
     90 
     91 # If container is a dictionary, retrieve the value for key item;
     92 # Otherwise, get the attribute named item out of container
     93 def getByAttributeOrKey(container, item, default=None):
     94     if isinstance(container, dict):
     95         try:
     96             return container[item]
     97         except KeyError as e:
     98             return default
     99     try:
    100         return getattr(container, item)
    101     except AttributeError as e:
    102         return default
    103 
    104 class Vhal:
    105     """
    106         Dictionary of prop_id to value_type.  Used by setProperty() to properly format data.
    107     """
    108     _propToType = {}
    109 
    110     ### Private Functions
    111     def _txCmd(self, cmd):
    112         """
    113             Transmits a protobuf to Android Auto device.  Should not be called externally.
    114         """
    115         # Serialize the protobuf into a string
    116         msgStr = cmd.SerializeToString()
    117         msgLen = len(msgStr)
    118         # Convert the message length into int32 byte array
    119         msgHdr = struct.pack('!I', msgLen)
    120         # Send the message length first
    121         self.sock.sendall(msgHdr)
    122         # Then send the protobuf
    123         self.sock.sendall(msgStr)
    124 
    125     ### Public Functions
    126     def printHex(self, data):
    127         """
    128             For debugging, print the protobuf message string in hex.
    129         """
    130         print("len = ", len(data), "str = ", ":".join("{:02x}".format(ord(d)) for d in data))
    131 
    132     def openSocket(self, device=None):
    133         """
    134             Connects to an Android Auto device running a Vehicle HAL with simulator.
    135         """
    136         # Hard-coded socket port needs to match the one in DefaultVehicleHal
    137         remotePortNumber = 33452
    138         extraArgs = '' if device is None else '-s %s' % device
    139         adbCmd = 'adb %s forward tcp:0 tcp:%d' % (extraArgs, remotePortNumber)
    140         adbResp = subprocess.check_output(adbCmd, shell=True)[0:-1]
    141         localPortNumber = int(adbResp)
    142         print('Connecting local port %s to remote port %s on %s' % (
    143             localPortNumber, remotePortNumber,
    144             'default device' if device is None else 'device %s' % device))
    145         # Open the socket and connect
    146         self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    147         self.sock.connect(('localhost', localPortNumber))
    148 
    149     def rxMsg(self):
    150         """
    151             Receive a message over the socket.  This function blocks if a message is not available.
    152               May want to wrap this function inside of an rx thread to also collect asynchronous
    153               messages generated by the device.
    154         """
    155         # Receive the message length (int32) first
    156         b = self.sock.recv(4)
    157         if (len(b) == 4):
    158             msgLen, = struct.unpack('!I', b)
    159             if (msgLen > 0):
    160                 # Receive the actual message
    161                 b = self.sock.recv(msgLen)
    162                 if (len(b) == msgLen):
    163                     # Unpack the protobuf
    164                     msg = VehicleHalProto_pb2.EmulatorMessage()
    165                     msg.ParseFromString(b)
    166                     return msg
    167                 else:
    168                     print("Ignored message fragment")
    169 
    170     def getConfig(self, prop):
    171         """
    172             Sends a getConfig message for the specified property.
    173         """
    174         cmd = VehicleHalProto_pb2.EmulatorMessage()
    175         cmd.msg_type = VehicleHalProto_pb2.GET_CONFIG_CMD
    176         propGet = cmd.prop.add()
    177         propGet.prop = prop
    178         self._txCmd(cmd)
    179 
    180     def getConfigAll(self):
    181         """
    182             Sends a getConfigAll message to the host.  This will return all configs available.
    183         """
    184         cmd = VehicleHalProto_pb2.EmulatorMessage()
    185         cmd.msg_type = VehicleHalProto_pb2.GET_CONFIG_ALL_CMD
    186         self._txCmd(cmd)
    187 
    188     def getProperty(self, prop, area_id):
    189         """
    190             Sends a getProperty command for the specified property ID and area ID.
    191         """
    192         cmd = VehicleHalProto_pb2.EmulatorMessage()
    193         cmd.msg_type = VehicleHalProto_pb2.GET_PROPERTY_CMD
    194         propGet = cmd.prop.add()
    195         propGet.prop = prop
    196         propGet.area_id = area_id
    197         self._txCmd(cmd)
    198 
    199     def getPropertyAll(self):
    200         """
    201             Sends a getPropertyAll message to the host.  This will return all properties available.
    202         """
    203         cmd = VehicleHalProto_pb2.EmulatorMessage()
    204         cmd.msg_type = VehicleHalProto_pb2.GET_PROPERTY_ALL_CMD
    205         self._txCmd(cmd)
    206 
    207     def setProperty(self, prop, area_id, value, status=VehicleHalProto_pb2.AVAILABLE):
    208         """
    209             Sends a setProperty command for the specified property ID, area ID, value and status.
    210               If Status is not specified, automatically send AVAILABLE as the default.
    211               This function chooses the proper value field to populate based on the config for the
    212               property.  It is the caller's responsibility to ensure the value data is the proper
    213               type.
    214         """
    215         cmd = VehicleHalProto_pb2.EmulatorMessage()
    216         cmd.msg_type = VehicleHalProto_pb2.SET_PROPERTY_CMD
    217         propValue = cmd.value.add()
    218         propValue.prop = prop
    219         # Insert value into the proper area
    220         propValue.area_id = area_id
    221         propValue.status = status;
    222         # Determine the value_type and populate the correct value field in protoBuf
    223         try:
    224             valType = self._propToType[prop]
    225         except KeyError:
    226             raise ValueError('propId is invalid:', prop)
    227             return
    228         propValue.value_type = valType
    229         if valType in self._types.TYPE_STRING:
    230             propValue.string_value = value
    231         elif valType in self._types.TYPE_BYTES:
    232             propValue.bytes_value = value
    233         elif valType in self._types.TYPE_INT32:
    234             propValue.int32_values.append(value)
    235         elif valType in self._types.TYPE_INT64:
    236             propValue.int64_values.append(value)
    237         elif valType in self._types.TYPE_FLOAT:
    238             propValue.float_values.append(value)
    239         elif valType in self._types.TYPE_INT32S:
    240             propValue.int32_values.extend(value)
    241         elif valType in self._types.TYPE_FLOATS:
    242             propValue.float_values.extend(value)
    243         elif valType in self._types.TYPE_MIXED:
    244             propValue.string_value = \
    245                 getByAttributeOrKey(value, 'string_value', '')
    246             propValue.bytes_value = \
    247                 getByAttributeOrKey(value, 'bytes_value', '')
    248             for newValue in getByAttributeOrKey(value, 'int32_values', []):
    249                 propValue.int32_values.append(newValue)
    250             for newValue in getByAttributeOrKey(value, 'int64_values', []):
    251                 propValue.int64_values.append(newValue)
    252             for newValue in getByAttributeOrKey(value, 'float_values', []):
    253                 propValue.float_values.append(newValue)
    254         else:
    255             raise ValueError('value type not recognized:', valType)
    256             return
    257         self._txCmd(cmd)
    258 
    259     def __init__(self, types, device=None):
    260         # Save the list of types constants
    261         self._types = types
    262         # Open the socket
    263         self.openSocket(device)
    264         # Get the list of configs
    265         self.getConfigAll()
    266         msg = self.rxMsg()
    267         # Parse the list of configs to generate a dictionary of prop_id to type
    268         for cfg in msg.config:
    269             self._propToType[cfg.prop] = cfg.value_type
    270