1#!/usr/bin/env python3 2# 3# Copyright (C) 2018 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); you may not 6# use this file except in compliance with the License. You may obtain a copy of 7# the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14# License for the specific language governing permissions and limitations under 15# the License. 16"""This file is used to send relay devices commands. 17 18Usage: 19 See --help for more details. 20 21 relay_tool.py -c <acts_config> -tb <testbed_name> -rd <device_name> -cmd cmd 22 Runs the command cmd for the given device pulled from the ACTS config. 23 24 relay_tool.py -c <acts_config> -tb <testbed_name> -rd <device_name> -l 25 Lists all possible functions (as well as documentation) for the device. 26 27Examples: 28 29 # For a Bluetooth Relay Device: 30 $ relay_tool.py -c ... -tb ... -rd ... -cmd get_mac_address 31 <MAC_ADDRESS> 32 relay_tool.py -c ... -tb ... -rd ... -cmd enter_pairing_mode 33 # No output. Waits for enter_pairing_mode to complete. 34""" 35 36import argparse 37import json 38import re 39import sys 40import inspect 41 42from acts.controllers import relay_device_controller 43 44 45def get_relay_device(config_path, testbed_name, device_name): 46 """Returns the relay device specified by the arguments. 47 48 Args: 49 config_path: the path to the ACTS config. 50 testbed_name: the name of the testbed the device is a part of. 51 device_name: the name of the device within the testbed. 52 53 Returns: 54 The RelayDevice object. 55 """ 56 with open(config_path) as config_file: 57 config = json.load(config_file) 58 59 relay_config = config['testbed'][testbed_name]['RelayDevice'] 60 relay_devices = relay_device_controller.create(relay_config) 61 62 try: 63 return next(device for device in relay_devices 64 if device.name == device_name) 65 except StopIteration: 66 # StopIteration is raised when no device is found. 67 all_device_names = [device.name for device in relay_devices] 68 print('Unable to find device with name "%s" in testbed "%s". Expected ' 69 'any of %s.' % (device_name, testbed_name, all_device_names), 70 file=sys.stderr) 71 raise 72 73 74def print_docstring(relay_device, func_name): 75 """Prints the docstring of the specified function to stderr. 76 77 Note that the documentation will be printed as follows: 78 79 func_name: 80 Docstring information, indented with a minimum of 4 spaces. 81 82 Args: 83 relay_device: the RelayDevice to find a function on. 84 func_name: the function to pull the docstring from. 85 """ 86 func = getattr(relay_device, func_name) 87 signature = inspect.signature(func) 88 docstring = func.__doc__ 89 if docstring is None: 90 docstring = ' No docstring available.' 91 else: 92 # Make the indentation uniform. 93 94 min_line_indentation = sys.maxsize 95 # Skip the first line, because docstrings begin with 'One liner.\n', 96 # instead of an indentation. 97 for line in docstring.split('\n')[1:]: 98 index = 0 99 for index, char in enumerate(line): 100 if char != ' ': 101 break 102 if index + 1 < min_line_indentation and index != 0: 103 min_line_indentation = index + 1 104 105 if min_line_indentation == sys.maxsize: 106 min_line_indentation = 0 107 108 min_indent = '\n' + ' ' * (min_line_indentation - 4) 109 docstring = ' ' * 4 + docstring.rstrip() 110 docstring = re.sub(min_indent, '\n', docstring, 111 flags=re.MULTILINE) 112 113 print('%s%s: \n%s\n' % (func_name, str(signature), docstring), 114 file=sys.stderr) 115 116 117def main(): 118 parser = argparse.ArgumentParser() 119 parser.add_argument('-c', '--config', type=str, required=True, 120 help='The path to the config file.') 121 parser.add_argument('-tb', '--testbed', type=str, required=True, 122 help='The testbed within the config file to use.') 123 parser.add_argument('-rd', '--relay_device', type=str, required=True, 124 help='The name of the relay device to use.') 125 group = parser.add_mutually_exclusive_group() 126 group.add_argument('-cmd', '--command', type=str, nargs='+', 127 help='The command to run on the relay device.') 128 group.add_argument('-l', '--list_commmands', 129 action='store_true', 130 help='lists all commands for the given device.') 131 132 args = parser.parse_args() 133 relay_device = get_relay_device(args.config, args.testbed, 134 args.relay_device) 135 136 func_names = [func_name 137 for func_name in dir(relay_device) 138 if (not func_name.startswith('_') and 139 not func_name.endswith('__') and 140 callable(getattr(relay_device, func_name)))] 141 142 if args.command: 143 if args.command[0] not in func_names: 144 print('Received command %s. Expected any of %s.' % 145 (repr(args.command[0]), repr(func_names)), file=sys.stderr) 146 else: 147 # getattr should not be able to fail here. 148 func = getattr(relay_device, args.command[0]) 149 try: 150 ret = func(*args.command[1:]) 151 if ret is not None: 152 print(ret) 153 except TypeError as e: 154 # The above call may raise TypeError if an incorrect number 155 # of args are passed. 156 if len(e.args) == 1 and func.__name__ in e.args[0]: 157 # If calling the function raised the TypeError, log this 158 # more informative message instead of the traceback. 159 print('Incorrect number of args passed to command "%s".' % 160 args.command[0], file=sys.stderr) 161 print_docstring(relay_device, args.command[0]) 162 else: 163 raise 164 165 else: # args.list_commands is set 166 print('Note: These commands are specific to the device given. ' 167 'Some of these commands may not work on other devices.\n', 168 file=sys.stderr) 169 for func_name in func_names: 170 print_docstring(relay_device, func_name) 171 exit(1) 172 173 174if __name__ == '__main__': 175 main() 176