Yu-Hsuan Hsu | db96ced | 2020-03-03 17:35:01 +0800 | [diff] [blame^] | 1 | #!/usr/bin/env python3 |
En-Shuo Hsu | a681451 | 2020-01-16 16:47:39 +0800 | [diff] [blame] | 2 | # -*- coding: utf-8 -*- |
Cheng-Yi Chiang | 3f9cb97 | 2015-11-23 17:43:59 +0800 | [diff] [blame] | 3 | # Copyright 2015 The Chromium OS Authors. All rights reserved. |
| 4 | # Use of this source code is governed by a BSD-style license that can be |
| 5 | # found in the LICENSE file. |
Cheng-Yi Chiang | 3f9cb97 | 2015-11-23 17:43:59 +0800 | [diff] [blame] | 6 | """A simple utility to connect to Chameleond in an interactive shell.""" |
| 7 | |
En-Shuo Hsu | 8fca32b | 2019-12-09 17:47:45 +0800 | [diff] [blame] | 8 | import atexit |
Cheng-Yi Chiang | 3f9cb97 | 2015-11-23 17:43:59 +0800 | [diff] [blame] | 9 | import argparse |
| 10 | import code |
En-Shuo Hsu | a681451 | 2020-01-16 16:47:39 +0800 | [diff] [blame] | 11 | import logging # pylint: disable=cros-logging-import |
Cheng-Yi Chiang | ab747d8 | 2016-11-22 15:39:12 +0800 | [diff] [blame] | 12 | import os |
Cheng-Yi Chiang | 3f9cb97 | 2015-11-23 17:43:59 +0800 | [diff] [blame] | 13 | import readline |
| 14 | import rlcompleter |
Cheng-Yi Chiang | ab747d8 | 2016-11-22 15:39:12 +0800 | [diff] [blame] | 15 | import subprocess |
Cheng-Yi Chiang | 852e13e | 2017-04-17 19:02:22 +0800 | [diff] [blame] | 16 | import time |
Yu-Hsuan Hsu | db96ced | 2020-03-03 17:35:01 +0800 | [diff] [blame^] | 17 | import xmlrpc.client |
Cheng-Yi Chiang | 3f9cb97 | 2015-11-23 17:43:59 +0800 | [diff] [blame] | 18 | |
Moja Hsu | 7ef568c | 2016-12-27 15:58:58 +0800 | [diff] [blame] | 19 | from audio.audio_value_detector import AudioValueDetector |
| 20 | |
En-Shuo Hsu | a681451 | 2020-01-16 16:47:39 +0800 | [diff] [blame] | 21 | TUNNEL_NAME = 'to_chameleon_tunnel' |
En-Shuo Hsu | 8fca32b | 2019-12-09 17:47:45 +0800 | [diff] [blame] | 22 | |
Cheng-Yi Chiang | 3f9cb97 | 2015-11-23 17:43:59 +0800 | [diff] [blame] | 23 | |
| 24 | def ShowMessages(proxy): |
| 25 | """Shows the messages for usage. |
| 26 | |
| 27 | Args: |
| 28 | proxy: The xmlrpclib.ServerProxy to chameleond. |
| 29 | """ |
| 30 | logging.info('In interactive shell, p is the proxy to chameleond server') |
| 31 | supported_ports = proxy.GetSupportedPorts() |
| 32 | linein_port = None |
| 33 | hdmi_port = None |
| 34 | port_messages = [] |
| 35 | for port in supported_ports: |
| 36 | port_type = proxy.GetConnectorType(port) |
| 37 | if port_type == 'LineIn': |
| 38 | linein_port = port |
| 39 | if port_type == 'HDMI': |
| 40 | hdmi_port = port |
| 41 | port_messages.append('Port %d is %s.' % (port, port_type)) |
En-Shuo Hsu | a681451 | 2020-01-16 16:47:39 +0800 | [diff] [blame] | 42 | message = """ |
Cheng-Yi Chiang | 3f9cb97 | 2015-11-23 17:43:59 +0800 | [diff] [blame] | 43 | %s |
En-Shuo Hsu | a681451 | 2020-01-16 16:47:39 +0800 | [diff] [blame] | 44 | E.g.""" % '\n '.join(port_messages) |
Moja Hsu | 28e1aab | 2017-03-30 15:24:35 +0800 | [diff] [blame] | 45 | if linein_port: |
En-Shuo Hsu | a681451 | 2020-01-16 16:47:39 +0800 | [diff] [blame] | 46 | message += """ |
Cheng-Yi Chiang | 3f9cb97 | 2015-11-23 17:43:59 +0800 | [diff] [blame] | 47 | p.StartCapturingAudio(%d) to capture from LineIn. |
En-Shuo Hsu | a681451 | 2020-01-16 16:47:39 +0800 | [diff] [blame] | 48 | p.StopCapturingAudio(%d) to stop capturing from LineIn.""" % (linein_port, |
En-Shuo Hsu | 8fca32b | 2019-12-09 17:47:45 +0800 | [diff] [blame] | 49 | linein_port) |
Moja Hsu | 28e1aab | 2017-03-30 15:24:35 +0800 | [diff] [blame] | 50 | |
| 51 | if hdmi_port: |
En-Shuo Hsu | a681451 | 2020-01-16 16:47:39 +0800 | [diff] [blame] | 52 | message += """ |
Cheng-Yi Chiang | 3f9cb97 | 2015-11-23 17:43:59 +0800 | [diff] [blame] | 53 | p.Plug(%d) to plug HDMI. |
En-Shuo Hsu | a681451 | 2020-01-16 16:47:39 +0800 | [diff] [blame] | 54 | p.Unplug(%d) to unplug HDMI.""" % (hdmi_port, hdmi_port) |
Moja Hsu | 28e1aab | 2017-03-30 15:24:35 +0800 | [diff] [blame] | 55 | |
| 56 | logging.info(message) |
Cheng-Yi Chiang | 3f9cb97 | 2015-11-23 17:43:59 +0800 | [diff] [blame] | 57 | |
| 58 | |
En-Shuo Hsu | 8fca32b | 2019-12-09 17:47:45 +0800 | [diff] [blame] | 59 | def DetectAudioValue0(channels=None, |
| 60 | margin=0.01, |
| 61 | continuous_samples=5, |
| 62 | duration=3600, |
| 63 | dump_samples=48000): |
Moja Hsu | 7ef568c | 2016-12-27 15:58:58 +0800 | [diff] [blame] | 64 | """Detects if Chameleon captures continuous audio data close to 0. |
| 65 | |
| 66 | This function will get the audio streaming data from stream server and will |
| 67 | check if the audio data is close to 0 by the margin parameter. |
| 68 | -margin < value < margin will be considered to be close to 0. |
| 69 | If there are continuous audio samples close to 0 in the streamed data, |
| 70 | test_server will log it and save the audio data to a wav file. |
| 71 | |
| 72 | E.g. |
| 73 | >>> ConnectCrosToLineIn() |
| 74 | >>> p.StartCapturingAudio(6, False) |
| 75 | >>> DetectAudioValue0(duration=24*3600, margin=0.001) |
| 76 | |
| 77 | Args: |
| 78 | channels: Array of audio channels we want to check. |
| 79 | E.g. [0, 1] means we only care about channel 0 and channel 1. |
| 80 | margin: Used to decide if the value is closed to 0. Maximum value is 1. |
| 81 | continuous_samples: When continuous_samples samples are closed to 0, trigger |
| 82 | event. |
| 83 | duration: The duration of monitoring in seconds. |
| 84 | dump_samples: When event happens, how many audio samples we want to |
| 85 | save to file. |
| 86 | """ |
| 87 | if not channels: |
| 88 | channels = [0, 1] |
| 89 | detecter = AudioValueDetector(options.host) # pylint: disable=undefined-variable |
| 90 | detecter.Detect(channels, margin, continuous_samples, duration, dump_samples) |
| 91 | return True |
| 92 | |
| 93 | |
En-Shuo Hsu | 8fca32b | 2019-12-09 17:47:45 +0800 | [diff] [blame] | 94 | def StartInteractiveShell(p): # pylint: disable=unused-argument |
Cheng-Yi Chiang | 3f9cb97 | 2015-11-23 17:43:59 +0800 | [diff] [blame] | 95 | """Starts an interactive shell. |
| 96 | |
| 97 | Args: |
| 98 | p: The xmlrpclib.ServerProxy to chameleond. |
| 99 | """ |
Moja Hsu | 7ef568c | 2016-12-27 15:58:58 +0800 | [diff] [blame] | 100 | vars = globals() # pylint: disable=redefined-builtin |
Cheng-Yi Chiang | 3f9cb97 | 2015-11-23 17:43:59 +0800 | [diff] [blame] | 101 | vars.update(locals()) |
| 102 | readline.set_completer(rlcompleter.Completer(vars).complete) |
En-Shuo Hsu | a681451 | 2020-01-16 16:47:39 +0800 | [diff] [blame] | 103 | readline.parse_and_bind('tab: complete') |
Cheng-Yi Chiang | 3f9cb97 | 2015-11-23 17:43:59 +0800 | [diff] [blame] | 104 | shell = code.InteractiveConsole(vars) |
| 105 | shell.interact() |
| 106 | |
| 107 | |
| 108 | def ParseArgs(): |
| 109 | """Parses the arguments. |
| 110 | |
Moja Hsu | 7ef568c | 2016-12-27 15:58:58 +0800 | [diff] [blame] | 111 | Returns: |
| 112 | the namespace containing parsed arguments. |
Cheng-Yi Chiang | 3f9cb97 | 2015-11-23 17:43:59 +0800 | [diff] [blame] | 113 | """ |
| 114 | parser = argparse.ArgumentParser( |
| 115 | description='Connect to Chameleond and use interactive shell.', |
| 116 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) |
En-Shuo Hsu | 8fca32b | 2019-12-09 17:47:45 +0800 | [diff] [blame] | 117 | parser.add_argument( |
| 118 | '--chameleon_host', |
| 119 | type=str, |
| 120 | dest='host', |
| 121 | required=True, |
| 122 | help='host address of Chameleond') |
| 123 | parser.add_argument( |
| 124 | '--port', |
| 125 | type=int, |
| 126 | dest='port', |
| 127 | default=9992, |
| 128 | help='port number of Chameleond') |
| 129 | parser.add_argument( |
| 130 | '--remote', |
| 131 | action='store_true', |
| 132 | help='Connect remotely. ' |
| 133 | 'Adding the flag will establish ssh tunnel automatically for you.') |
| 134 | parser.add_argument( |
| 135 | '--local_port', |
| 136 | type=int, |
| 137 | default=12346, |
| 138 | help='port number of localhost. This will only be used ' |
| 139 | 'if you enable --remote.') |
Cheng-Yi Chiang | 3f9cb97 | 2015-11-23 17:43:59 +0800 | [diff] [blame] | 140 | return parser.parse_args() |
| 141 | |
| 142 | |
Cheng-Yi Chiang | ab747d8 | 2016-11-22 15:39:12 +0800 | [diff] [blame] | 143 | def GetAndConvertRecordedFile(remote_path): |
| 144 | """Gets recorded file and converts it into a wav file. |
| 145 | |
| 146 | A helper function to get recorded file from Chameleon host and do |
| 147 | file format conversion from 32 bit, 48000 rate, 8 channel raw file |
| 148 | to 2 channel wav file. |
| 149 | |
| 150 | E.g. |
| 151 | >>> p.StartCapturingAudio(6) |
| 152 | >>> s = p.StopCapturingAudio(6) |
| 153 | >>> GetAndConvertRecordedFile(s[0]) |
| 154 | |
| 155 | The recorded raw file and converted wav file will be in current |
| 156 | directory. |
| 157 | |
| 158 | Args: |
| 159 | remote_path: The file to copy from Chameleon host. |
Cheng-Yi Chiang | ab747d8 | 2016-11-22 15:39:12 +0800 | [diff] [blame] | 160 | """ |
| 161 | basename = os.path.basename(remote_path) |
| 162 | # options is already in the namespace. |
| 163 | subprocess.check_call( |
Moja Hsu | 7ef568c | 2016-12-27 15:58:58 +0800 | [diff] [blame] | 164 | ['scp', 'root@%s:%s' % (options.host, remote_path), basename]) # pylint: disable=undefined-variable |
En-Shuo Hsu | 8fca32b | 2019-12-09 17:47:45 +0800 | [diff] [blame] | 165 | subprocess.check_call([ |
| 166 | 'sox', '-b', '32', '-r', '48000', '-c', '8', '-e', 'signed', basename, |
| 167 | '-c', '2', basename + '.wav' |
| 168 | ]) |
Cheng-Yi Chiang | ab747d8 | 2016-11-22 15:39:12 +0800 | [diff] [blame] | 169 | |
| 170 | |
En-Shuo Hsu | a681451 | 2020-01-16 16:47:39 +0800 | [diff] [blame] | 171 | def ConnectCrosToLineIn(): |
| 172 | """Connects a audio bus path from Cros headphone to Chameleon LineIn.""" |
En-Shuo Hsu | 8fca32b | 2019-12-09 17:47:45 +0800 | [diff] [blame] | 173 | p.AudioBoardConnect(1, 'Cros device headphone') # pylint: disable=undefined-variable |
| 174 | p.AudioBoardConnect(1, 'Chameleon FPGA line-in') # pylint: disable=undefined-variable |
Cheng-Yi Chiang | ab747d8 | 2016-11-22 15:39:12 +0800 | [diff] [blame] | 175 | |
| 176 | |
Cheng-Yi Chiang | 852e13e | 2017-04-17 19:02:22 +0800 | [diff] [blame] | 177 | def TestMotors(): |
| 178 | """Test motors by touching and releasing each button once.""" |
| 179 | for func in ['Call', 'Hang Up', 'Mute', 'Vol Up', 'Vol Down']: |
| 180 | PressOneFunc(func) |
| 181 | |
| 182 | |
| 183 | def PressOneFunc(func, time_sec=0): |
| 184 | """Test motors by touching and releasing one button. |
| 185 | |
| 186 | Args: |
| 187 | func: The motor function. One of 'Call', 'Hang Up', 'Mute', 'Vol Up', |
| 188 | 'Vol Down'. |
| 189 | time_sec: Hold time in seconds after touch and before release. |
| 190 | """ |
En-Shuo Hsu | 8fca32b | 2019-12-09 17:47:45 +0800 | [diff] [blame] | 191 | logging.info('Testing %s button, press and hold for %f seconds', func, |
| 192 | time_sec) |
En-Shuo Hsu | a681451 | 2020-01-16 16:47:39 +0800 | [diff] [blame] | 193 | p.motor_board.Touch(func) # pylint: disable=undefined-variable |
Cheng-Yi Chiang | 852e13e | 2017-04-17 19:02:22 +0800 | [diff] [blame] | 194 | time.sleep(time_sec) |
En-Shuo Hsu | a681451 | 2020-01-16 16:47:39 +0800 | [diff] [blame] | 195 | p.motor_board.Release(func) # pylint: disable=undefined-variable |
Cheng-Yi Chiang | 852e13e | 2017-04-17 19:02:22 +0800 | [diff] [blame] | 196 | |
| 197 | |
En-Shuo Hsu | 8fca32b | 2019-12-09 17:47:45 +0800 | [diff] [blame] | 198 | def BuildTunnel(local_port, port, host): |
| 199 | |
| 200 | def cleanup(): |
| 201 | cleanup_cmd = 'ssh -S %s -O exit root@%s' % (TUNNEL_NAME, host) |
| 202 | subprocess.call(cleanup_cmd, shell=True) |
| 203 | |
| 204 | cmd = ('ssh -M -S %s -fnNT -o "StrictHostKeyChecking no" ' |
| 205 | '-L %d:localhost:%d root@%s') % (TUNNEL_NAME, local_port, port, host) |
| 206 | return None if subprocess.call(cmd, shell=True) else cleanup |
| 207 | |
| 208 | |
Cheng-Yi Chiang | 3f9cb97 | 2015-11-23 17:43:59 +0800 | [diff] [blame] | 209 | def Main(): |
| 210 | """The Main program.""" |
| 211 | logging.basicConfig( |
| 212 | format='%(asctime)s:%(levelname)s:%(message)s', level=logging.DEBUG) |
| 213 | |
| 214 | options = ParseArgs() |
| 215 | |
En-Shuo Hsu | 8fca32b | 2019-12-09 17:47:45 +0800 | [diff] [blame] | 216 | if options.remote: |
| 217 | cleanup = BuildTunnel(options.local_port, options.port, options.host) |
| 218 | if cleanup is None: |
En-Shuo Hsu | a681451 | 2020-01-16 16:47:39 +0800 | [diff] [blame] | 219 | logging.info('Failed to create tunnel') |
En-Shuo Hsu | 8fca32b | 2019-12-09 17:47:45 +0800 | [diff] [blame] | 220 | return |
| 221 | atexit.register(cleanup) |
| 222 | address = 'http://localhost:%d' % options.local_port |
| 223 | else: |
| 224 | address = 'http://%s:%s' % (options.host, options.port) |
| 225 | |
Yu-Hsuan Hsu | db96ced | 2020-03-03 17:35:01 +0800 | [diff] [blame^] | 226 | proxy = xmlrpc.client.ServerProxy(address) |
En-Shuo Hsu | 8fca32b | 2019-12-09 17:47:45 +0800 | [diff] [blame] | 227 | logging.info('Connected to %s with MAC address %s', address, |
| 228 | proxy.GetMacAddress()) |
Cheng-Yi Chiang | 3f9cb97 | 2015-11-23 17:43:59 +0800 | [diff] [blame] | 229 | ShowMessages(proxy) |
En-Shuo Hsu | 8fca32b | 2019-12-09 17:47:45 +0800 | [diff] [blame] | 230 | StartInteractiveShell(proxy) |
Cheng-Yi Chiang | 3f9cb97 | 2015-11-23 17:43:59 +0800 | [diff] [blame] | 231 | |
| 232 | |
| 233 | if __name__ == '__main__': |
| 234 | Main() |