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