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 communicted with the Android 22 device. 23 24 Example Usage: 25 26 import vhal_consts_1_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_1_0) 31 32 # Get the property config (if desired) 33 v.getConfig(c.VEHICLE_PROPERTY_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.VEHICLE_PROPERTY_HVAC_TEMPERATURE_SET, c.VEHICLE_ZONE_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.VEHICLE_PROPERTY_HVAC_TEMPERATURE_SET, c.VEHICLE_ZONE_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 messags 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# Suppress .pyc files 76import sys 77sys.dont_write_bytecode = True 78 79import socket 80import struct 81import subprocess 82 83# Generate the protobuf file from vendor/auto/embedded/lib/vehicle_hal: 84# protoc -I=proto --python_out=proto proto/VehicleHalProto.proto 85import VehicleHalProto_pb2 86 87 88class Vhal: 89 """ 90 Dictionary of prop_id to value_type. Used by setProperty() to properly format data. 91 """ 92 _propToType = {} 93 94 ### Private Functions 95 def _txCmd(self, cmd): 96 """ 97 Transmits a protobuf to Android Auto device. Should not be called externally. 98 """ 99 # Serialize the protobuf into a string 100 msgStr = cmd.SerializeToString() 101 msgLen = len(msgStr) 102 # Convert the message length into int32 byte array 103 msgHdr = struct.pack('!I', msgLen) 104 # Send the message length first 105 self.sock.send(msgHdr) 106 # Then send the protobuf 107 self.sock.send(msgStr) 108 109 ### Public Functions 110 def printHex(self, data): 111 """ 112 For debugging, print the protobuf message string in hex. 113 """ 114 print "len = ", len(data), "str = ", ":".join("{:02x}".format(ord(d)) for d in data) 115 116 def openSocket(self): 117 """ 118 Connects to an Android Auto device running a Vehicle HAL with simulator. 119 """ 120 # Hard-coded socket port needs to match the one in DefaultVehicleHal 121 portNumber = 33452 122 # Setup ADB port forwarding 123 subprocess.call("adb forward tcp:%d tcp:%d" % (portNumber, portNumber), shell=True) 124 # Open the socket and connect 125 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 126 self.sock.connect(('localhost', portNumber)) 127 128 def rxMsg(self): 129 """ 130 Receive a message over the socket. This function blocks if a message is not available. 131 May want to wrap this function inside of an rx thread to also collect asynchronous 132 messages generated by the device. 133 """ 134 # Receive the message length (int32) first 135 b = self.sock.recv(4) 136 if (len(b) == 4): 137 msgLen, = struct.unpack('!I', b) 138 if (msgLen > 0): 139 # Receive the actual message 140 b = self.sock.recv(msgLen) 141 if (len(b) == msgLen): 142 # Unpack the protobuf 143 msg = VehicleHalProto_pb2.EmulatorMessage() 144 msg.ParseFromString(b) 145 return msg 146 147 def getConfig(self, prop): 148 """ 149 Sends a getConfig message for the specified property. 150 """ 151 cmd = VehicleHalProto_pb2.EmulatorMessage() 152 cmd.msg_type = VehicleHalProto_pb2.GET_CONFIG_CMD 153 propGet = cmd.prop.add() 154 propGet.prop = prop 155 self._txCmd(cmd) 156 157 def getConfigAll(self): 158 """ 159 Sends a getConfigAll message to the host. This will return all configs avaialable. 160 """ 161 cmd = VehicleHalProto_pb2.EmulatorMessage() 162 cmd.msg_type = VehicleHalProto_pb2.GET_CONFIG_ALL_CMD 163 self._txCmd(cmd) 164 165 def getProperty(self, prop, area_id): 166 """ 167 Sends a getProperty command for the specified property ID and area ID. 168 """ 169 cmd = VehicleHalProto_pb2.EmulatorMessage() 170 cmd.msg_type = VehicleHalProto_pb2.GET_PROPERTY_CMD 171 propGet = cmd.prop.add() 172 propGet.prop = prop 173 propGet.area_id = area_id 174 self._txCmd(cmd) 175 176 def getPropertyAll(self): 177 """ 178 Sends a getPropertyAll message to the host. This will return all properties avaialable. 179 """ 180 cmd = VehicleHalProto_pb2.EmulatorMessage() 181 cmd.msg_type = VehicleHalProto_pb2.GET_PROPERTY_ALL_CMD 182 self._txCmd(cmd) 183 184 def setProperty(self, prop, area_id, value): 185 """ 186 Sends a setProperty command for the specified property ID, area ID, and value. 187 This function chooses the proper value field to populate based on the config for the 188 property. It is the caller's responsibility to ensure the value data is the proper 189 type. 190 """ 191 cmd = VehicleHalProto_pb2.EmulatorMessage() 192 cmd.msg_type = VehicleHalProto_pb2.SET_PROPERTY_CMD 193 propValue = cmd.value.add() 194 propValue.prop = prop 195 # Insert value into the proper area 196 propValue.area_id = area_id 197 # Determine the value_type and populate the correct value field in protoBuf 198 try: 199 valType = self._propToType[prop] 200 except KeyError: 201 raise ValueError('propId is invalid:', prop) 202 return 203 propValue.value_type = valType 204 if valType in self._types.TYPE_STRING: 205 propValue.string_value = value 206 elif valType in self._types.TYPE_BYTES: 207 propValue.bytes_value = value 208 elif valType in self._types.TYPE_INT32: 209 propValue.int32_values.append(value) 210 elif valType in self._types.TYPE_INT64: 211 propValue.int64_values.append(value) 212 elif valType in self._types.TYPE_FLOAT: 213 propValue.float_values.append(value) 214 elif valType in self._types.TYPE_INT32S: 215 propValue.int32_values.extend(value) 216 elif valType in self._types.TYPE_FLOATS: 217 propValue.float_values.extend(value) 218 else: 219 raise ValueError('value type not recognized:', valType) 220 return 221 self._txCmd(cmd) 222 223 def __init__(self, types): 224 # Save the list of types constants 225 self._types = types 226 # Open the socket 227 self.openSocket() 228 # Get the list of configs 229 self.getConfigAll() 230 msg = self.rxMsg() 231 # Parse the list of configs to generate a dictionary of prop_id to type 232 for cfg in msg.config: 233 self._propToType[cfg.prop] = cfg.value_type 234 235