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