blob: 3952155c8ef706327aebde500f6d0f2209438c44 [file] [log] [blame]
Steve Paik7861f4e2017-02-22 15:02:41 -08001#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
Eric Jeongdfe5b862021-11-11 11:39:33 -08004# Copyright 2016 The Android Open Source Project
Steve Paik7861f4e2017-02-22 15:02:41 -08005#
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
Scott Randolph4ce965a2017-05-08 15:52:34 -070021 on an Android Auto device. It uses port forwarding via ADB to communicate with the Android
Steve Paik7861f4e2017-02-22 15:02:41 -080022 device.
23
24 Example Usage:
25
Enrico Granata5e067122017-03-29 12:05:30 -070026 import vhal_consts_2_0 as c
Steve Paik7861f4e2017-02-22 15:02:41 -080027 from vhal_emulator import Vhal
28
29 # Create an instance of vhal class. Need to pass the vhal_types constants.
Enrico Granata5e067122017-03-29 12:05:30 -070030 v = Vhal(c.vhal_types_2_0)
Steve Paik7861f4e2017-02-22 15:02:41 -080031
32 # Get the property config (if desired)
Scott Randolph4ce965a2017-05-08 15:52:34 -070033 v.getConfig(c.VEHICLEPROPERTY_HVAC_TEMPERATURE_SET)
Steve Paik7861f4e2017-02-22 15:02:41 -080034
35 # Get the response message to getConfig()
36 reply = v.rxMsg()
Enrico Granata66d5a972017-04-05 16:48:08 -070037 print(reply)
Steve Paik7861f4e2017-02-22 15:02:41 -080038
39 # Set left temperature to 70 degrees
Scott Randolph4ce965a2017-05-08 15:52:34 -070040 v.setProperty(c.VEHICLEPROPERTY_HVAC_TEMPERATURE_SET, c.VEHICLEAREAZONE_ROW_1_LEFT, 70)
Steve Paik7861f4e2017-02-22 15:02:41 -080041
42 # Get the response message to setProperty()
43 reply = v.rxMsg()
Enrico Granata66d5a972017-04-05 16:48:08 -070044 print(reply)
Steve Paik7861f4e2017-02-22 15:02:41 -080045
46 # Get the left temperature value
Scott Randolph4ce965a2017-05-08 15:52:34 -070047 v.getProperty(c.VEHICLEPROPERTY_HVAC_TEMPERATURE_SET, c.VEHICLEAREAZONE_ROW_1_LEFT)
Steve Paik7861f4e2017-02-22 15:02:41 -080048
49 # Get the response message to getProperty()
50 reply = v.rxMsg()
Enrico Granata66d5a972017-04-05 16:48:08 -070051 print(reply)
Steve Paik7861f4e2017-02-22 15:02:41 -080052
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
Scott Randolph18baf5f2017-06-06 15:54:27 -070060 # Define a simple thread that receives messages from a vhal object (v) and prints them
Steve Paik7861f4e2017-02-22 15:02:41 -080061 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
Enrico Granata66d5a972017-04-05 16:48:08 -070075from __future__ import print_function
76
Steve Paik7861f4e2017-02-22 15:02:41 -080077# Suppress .pyc files
78import sys
79sys.dont_write_bytecode = True
80
81import socket
82import struct
83import subprocess
84
Enrico Granata66d5a972017-04-05 16:48:08 -070085# 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
Steve Paik7861f4e2017-02-22 15:02:41 -080088# protoc -I=proto --python_out=proto proto/VehicleHalProto.proto
89import VehicleHalProto_pb2
90
Enrico Granata5e067122017-03-29 12:05:30 -070091# 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
Steve Paik7861f4e2017-02-22 15:02:41 -0800103
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
Scott Randolph95fb30b2017-05-11 17:32:26 -0700121 self.sock.sendall(msgHdr)
Steve Paik7861f4e2017-02-22 15:02:41 -0800122 # Then send the protobuf
Scott Randolph95fb30b2017-05-11 17:32:26 -0700123 self.sock.sendall(msgStr)
Steve Paik7861f4e2017-02-22 15:02:41 -0800124
125 ### Public Functions
126 def printHex(self, data):
127 """
128 For debugging, print the protobuf message string in hex.
129 """
Enrico Granata66d5a972017-04-05 16:48:08 -0700130 print("len = ", len(data), "str = ", ":".join("{:02x}".format(ord(d)) for d in data))
Steve Paik7861f4e2017-02-22 15:02:41 -0800131
Enrico Granata9b6111d2017-06-29 11:36:17 -0700132 def openSocket(self, device=None):
Steve Paik7861f4e2017-02-22 15:02:41 -0800133 """
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
Enrico Granata9b6111d2017-06-29 11:36:17 -0700137 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))
Steve Paik7861f4e2017-02-22 15:02:41 -0800145 # Open the socket and connect
146 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Enrico Granata9b6111d2017-06-29 11:36:17 -0700147 self.sock.connect(('localhost', localPortNumber))
Steve Paik7861f4e2017-02-22 15:02:41 -0800148
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
Scott Randolph95fb30b2017-05-11 17:32:26 -0700167 else:
168 print("Ignored message fragment")
Steve Paik7861f4e2017-02-22 15:02:41 -0800169
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 """
Scott Randolph02f7a322017-05-08 17:41:54 -0700182 Sends a getConfigAll message to the host. This will return all configs available.
Steve Paik7861f4e2017-02-22 15:02:41 -0800183 """
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 """
Scott Randolph02f7a322017-05-08 17:41:54 -0700201 Sends a getPropertyAll message to the host. This will return all properties available.
Steve Paik7861f4e2017-02-22 15:02:41 -0800202 """
203 cmd = VehicleHalProto_pb2.EmulatorMessage()
204 cmd.msg_type = VehicleHalProto_pb2.GET_PROPERTY_ALL_CMD
205 self._txCmd(cmd)
206
Steve Paike6a9a4d2018-01-25 16:34:22 -0800207 def setProperty(self, prop, area_id, value, status=VehicleHalProto_pb2.AVAILABLE):
Steve Paik7861f4e2017-02-22 15:02:41 -0800208 """
Steve Paike6a9a4d2018-01-25 16:34:22 -0800209 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.
Steve Paik7861f4e2017-02-22 15:02:41 -0800211 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
Steve Paike6a9a4d2018-01-25 16:34:22 -0800221 propValue.status = status;
Steve Paik7861f4e2017-02-22 15:02:41 -0800222 # 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)
Steve Paike6a9a4d2018-01-25 16:34:22 -0800243 elif valType in self._types.TYPE_MIXED:
Enrico Granata5e067122017-03-29 12:05:30 -0700244 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)
Steve Paik7861f4e2017-02-22 15:02:41 -0800254 else:
255 raise ValueError('value type not recognized:', valType)
256 return
257 self._txCmd(cmd)
258
Enrico Granata9b6111d2017-06-29 11:36:17 -0700259 def __init__(self, types, device=None):
Steve Paik7861f4e2017-02-22 15:02:41 -0800260 # Save the list of types constants
261 self._types = types
262 # Open the socket
Enrico Granata9b6111d2017-06-29 11:36:17 -0700263 self.openSocket(device)
Steve Paik7861f4e2017-02-22 15:02:41 -0800264 # 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