blob: f897f462fbbc5982016ae1ba6b0b946b9a5203e5 [file] [log] [blame]
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Map Bluetooth PTS Man Machine Interface to Pandora gRPC calls."""
__version__ = "0.0.1"
from typing import List
import time
import sys
import grpc
from mmi2grpc.a2dp import A2DPProxy
from mmi2grpc._helpers import format_proxy
from pandora.host_grpc import Host
GRPC_PORT = 8999
MAX_RETRIES = 10
class IUT:
"""IUT class.
Handles MMI calls from the PTS and routes them to corresponding profile
proxy which translates MMI calls to gRPC calls to the IUT.
"""
def __init__(
self, test: str, args: List[str], port: int = GRPC_PORT, **kwargs):
"""Init IUT class for a given test.
Args:
test: PTS test id.
args: test arguments.
port: gRPC port exposed by the IUT test server.
"""
self.port = port
self.test = test
# Profile proxies.
self._a2dp = None
def __enter__(self):
"""Resets the IUT when starting a PTS test."""
# Note: we don't keep a single gRPC channel instance in the IUT class
# because reset is allowed to close the gRPC server.
with grpc.insecure_channel(f'localhost:{self.port}') as channel:
Host(channel).Reset(wait_for_ready=True)
def __exit__(self, exc_type, exc_value, exc_traceback):
self._a2dp = None
@property
def address(self) -> bytes:
"""Bluetooth MAC address of the IUT."""
with grpc.insecure_channel(f'localhost:{self.port}') as channel:
tries = 0
while True:
try:
return Host(channel).ReadLocalAddress(
wait_for_ready=True).address
except grpc.RpcError or grpc._channel._InactiveRpcError:
tries += 1
if tries >= MAX_RETRIES:
raise
else:
print('Retry', tries, 'of', MAX_RETRIES)
time.sleep(1)
def interact(self,
pts_address: bytes,
profile: str,
test: str,
interaction: str,
description: str,
style: str,
**kwargs) -> str:
"""Routes MMI calls to corresponding profile proxy.
Args:
pts_address: Bluetooth MAC addres of the PTS in bytes.
profile: Bluetooth profile.
test: PTS test id.
interaction: MMI name.
description: MMI description.
style: MMI popup style, unused for now.
"""
print(f'{profile} mmi: {interaction}', file=sys.stderr)
# Handles A2DP and AVDTP MMIs.
if profile in ('A2DP', 'AVDTP'):
if not self._a2dp:
self._a2dp = A2DPProxy(
grpc.insecure_channel(f'localhost:{self.port}'))
return self._a2dp.interact(
test, interaction, description, pts_address)
# Handles unsupported profiles.
code = format_proxy(profile, interaction, description)
error_msg = (
f'Missing {profile} proxy and mmi: {interaction}\n'
f'Create a {profile.lower()}.py in mmi2grpc/:\n\n{code}\n'
f'Then, instantiate the corresponding proxy in __init__.py\n'
f'Finally, create a {profile.lower()}.proto in proto/pandora/'
f'and generate the corresponding interface.')
assert False, error_msg