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 75from __future__ import print_function 76 77# Suppress .pyc files 78import sys 79sys.dont_write_bytecode = True 80 81import socket 82import struct 83import 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 89import 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 93def 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 104class 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