blob: 4b5964974f1b30f19e72e2a75fcccea551502cd0 [file] [log] [blame]
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +08001# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
Ruben Rodriguez Buchillon0902f092018-09-19 15:03:17 +08004"""Common code for servo parsing support."""
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +08005
6import argparse
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +08007import logging
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +08008import os
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +08009import textwrap
10
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +080011import client
12import servo_logging
Ruben Rodriguez Buchillonbb65ba82020-03-20 13:27:43 -070013import sversion_util
Ruben Rodriguez Buchillon479bd242020-02-14 18:58:30 -080014import utils.scratch
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +080015
Ruben Rodriguez Buchillon014d8822018-09-20 10:12:13 +080016
17# A brief overview of the classes found here, and their abilities.
18# Indentation indicates inheritance.
19#
20# _BaseServodParser: ArgumentParser with pretty-formatting for example list
21#
Ruben Rodriguez Buchillonbb65ba82020-03-20 13:27:43 -070022# BaseServodParser: adds common args: port, host, version, and debug
Ruben Rodriguez Buchillon014d8822018-09-20 10:12:13 +080023#
24# ServodRCParser: adds -name/-rcfile & overwrites parsing logic so that
25# rc parsing & configuration is handled internally
26# i.e. the program does not need to know anything about
27# the servodrc system
28#
29# ServodClientParser: parser intended for servod clients (e.g. dut-control)
30# - has all the BaseServodParser and ServodRCParser args
31# - has the native rc configuration handling
32# - adds a serialname argument that gets mapped to a
33# port using ServoScratch
34
35
36# This text file holds servod configuration parameters. This is especially
37# handy for multi servo operation.
38#
39# The file format is pretty loose:
40# - text starting with # is ignored til the end of the line
41# - empty lines are ignored
42# - configuration lines consist of up to 5 comma separated fields (all
43# but the first field optional):
44# servo-name, serial-number, port-number, board-name, board-model
45#
46# where
47# . servo-name - a user defined symbolic name, just a reference
48# to a certain servo board
49# . serial-number - serial number of the servo board this line pertains to
50# . port-number - desired port number for servod for this board, can be
51# overridden by the command line switch --port or
52# environment variable setting SERVOD_PORT
53# NOTE: this is no longer in use, and will be ignored.
54# . board-name - board configuration file to use, can be
55# overridden by the command line switch --board
56# . model-name - model override to use, if applicable.
57# overridden by command line --model
58#
59# Example lines in the rc-file:
60# nocturne_micro, SNCQ00098, , nocturne # This is a nocturne without model
61# octopus_micro, SNCQ00098, , octopus, npcx # This an octopus that defines model
62#
63# As you can see, the port part is left out for now. This will be phased out
64# giving users time to adjust their rc files.
65#
66# Since the same parameters could be defined using different means, there is a
67# hierarchy of definitions (left being the highest priority):
68# command line <- environment definition <- rc config file
69
70
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +080071if os.getuid():
72 DEFAULT_RC_FILE = '/home/%s/.servodrc' % os.getenv('USER', '')
73else:
74 DEFAULT_RC_FILE = '/home/%s/.servodrc' % os.getenv('SUDO_USER', '')
75
76
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +080077PORT_ENV_VAR = 'SERVOD_PORT'
78NAME_ENV_VAR = 'SERVOD_NAME'
79
80
81ARG_BY_USER_MARKER = 'supplied_by_user'
82
Ruben Rodriguez Buchillonbb65ba82020-03-20 13:27:43 -070083# Keep track of both the 'version' according to PEP440 and 'sversion' our
84# internal version system, and provide arguments to print those.
85VERSION = '%(prog)s ' + sversion_util.setuptools_version()
86SVERSION = '%(prog)s ' + sversion_util.extended_version()
87
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +080088
89def ArgMarkedAsUserSupplied(namespace, arg_name):
90 """Query whether an argument that uses StoreAndMarkAction is user supplied."""
91 marker_name = '%s_%s' % (arg_name, ARG_BY_USER_MARKER)
92 return hasattr(namespace, marker_name)
93
94# pylint: disable=protected-access
95# Need to expand the StoreAction of the parser.
96class StoreAndMarkAction(argparse._StoreAction):
97 """Helper to mark arguments whether they were supplied by the user.
98
99 If an argument is supplied by the user instead of using defaults or RC,
100 add another option with the name |arg|_supplied_by_user.
101 """
102
103 def __call__(self, parser, namespace, values, option_string=None):
104 """Extend default __call__ implementation."""
105 # This sets the |values| to |self.dest|.
106 super(StoreAndMarkAction, self).__call__(parser=parser, namespace=namespace,
107 values=values,
108 option_string=option_string)
109 marker_name = '%s_%s' % (self.dest, ARG_BY_USER_MARKER)
110 setattr(namespace, marker_name, True)
111
112
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800113class ServodParserHelpFormatter(argparse.RawDescriptionHelpFormatter,
114 argparse.ArgumentDefaultsHelpFormatter):
115 """Servod help formatter.
116
117 Combines ability for raw description printing (needed to have control over
118 how to print examples) and default argument printing, printing the default
119 which each argument.
120 """
121 pass
122
123
124class ServodParserError(Exception):
125 """Error class for Servod parsing errors."""
126 pass
127
128
129class _BaseServodParser(argparse.ArgumentParser):
130 """Extension to ArgumentParser that allows for examples in the description.
131
132 _BaseServodParser allows for a list of example tuples, where
133 element[0]: is the cmdline invocation
134 element[1]: is a comment to explain what the invocation does.
135
136 For example (loosely based on servod.)
137 ('-b board', 'Start servod with the configuation for board |board|')
138 would print the following help message:
139 ...
140
141 Examples:
142 > servod -b board
143 Start servod with the configuration for board |board|
144
145 Optional Arguments...
146
147 see servod, or dut_control for more examples.
148 """
149
Ruben Rodriguez Buchillon0902f092018-09-19 15:03:17 +0800150 def __init__(self, description='', examples=None, **kwargs):
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800151 """Initialize _BaseServodParser by setting description and formatter.
152
153 Args:
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800154 description: description of the program
155 examples: list of tuples where the first element is the cmdline example,
156 and the second element is a comment explaining the example.
157 %(prog)s will be prepended to each example if it does not
158 start with %(prog)s.
Ruben Rodriguez Buchillon0902f092018-09-19 15:03:17 +0800159 **kwargs: keyword arguments forwarded to ArgumentParser
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800160 """
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800161 # Initialize logging up here first to ensure log messages from parsing
162 # can go through.
163 loglevel, fmt = servo_logging.LOGLEVEL_MAP[servo_logging.DEFAULT_LOGLEVEL]
Ruben Rodriguez Buchillon56b1f3f2020-05-13 14:56:41 -0700164 logging.basicConfig(level=loglevel, format=fmt)
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800165 self._logger = logging.getLogger(type(self).__name__)
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800166 # Generate description.
167 description_lines = textwrap.wrap(description)
168 # Setting it into the kwargs here ensures that we overwrite an potentially
169 # passed in and undesired formatter class.
170 kwargs['formatter_class'] = ServodParserHelpFormatter
171 if examples:
172 # Extra newline to separate description from examples.
173 description_lines.append('\n')
174 description_lines.append('Examples:')
175 for example, comment in examples:
176 if not example.startswith('%(prog)s'):
177 example = '%(prog)s ' + example
178 example_lines = [' > ' + example]
179 example_lines.extend(textwrap.wrap(comment))
180 description_lines.append('\n\t'.join(example_lines))
181 description = '\n'.join(description_lines)
182 kwargs['description'] = description
Ruben Rodriguez Buchillon0902f092018-09-19 15:03:17 +0800183 super(_BaseServodParser, self).__init__(**kwargs)
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800184
185
186class BaseServodParser(_BaseServodParser):
187 """BaseServodParser handling common arguments in the servod cmdline tools."""
188
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800189 def __init__(self, add_port=True, **kwargs):
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800190 """Initialize by adding common arguments.
191
192 Adds:
193 - host/port arguments to find/initialize a servod instance
194 - debug argument to toggle debug message printing
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800195
196 Args:
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800197 add_port: bool, whether to add --port to the parser. A caller might want
198 to add port themselves either to rename it (servod-port),
199 or to create mutual exclusion with serialname and name (clients)
Ruben Rodriguez Buchillon0902f092018-09-19 15:03:17 +0800200 **kwargs: keyword arguments forwarded to _BaseServodParser
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800201 """
Ruben Rodriguez Buchillon2187edd2020-11-18 17:03:56 -0800202 # Remove version from the kwargs and add it yourself.
203 v = kwargs.pop('version', VERSION)
Ruben Rodriguez Buchillon0902f092018-09-19 15:03:17 +0800204 super(BaseServodParser, self).__init__(**kwargs)
Ruben Rodriguez Buchillon2187edd2020-11-18 17:03:56 -0800205 self.add_argument('-v', '--version', action='version', version=v)
Ruben Rodriguez Buchillonbb65ba82020-03-20 13:27:43 -0700206 self.add_argument('--sversion', action='version', version=SVERSION)
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800207 self.add_argument('-d', '--debug', action='store_true', default=False,
208 help='enable debug messages')
209 self.add_argument('--host', default='localhost', type=str,
210 help='hostname of the servod server.')
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800211 if add_port:
212 BaseServodParser.AddRCEnabledPortArg(self)
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800213
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800214 @staticmethod
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800215 def AddRCEnabledPortArg(parser, port_flags=['-p', '--port']):
216 """Add the port to the argparser.
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800217
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800218 Set the default to environment variable ENV_PORT_NAME if defined
219
220 Note: while this helper does allow for arbitrary flags for the port
221 variable, the destination is still set to 'port'. It's on the caller to
222 ensure that there is no conflict.
Ruben Rodriguez Buchillon0902f092018-09-19 15:03:17 +0800223
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800224 Args:
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800225 parser: parser or group to add argument to
226 port_flags: optional, list, if the flags for the port should be different
227 than the default ones.
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800228 """
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800229 # pylint: disable=dangerous-default-value
230 # Having the default flags here simplifies the code logic.
231 default = os.environ.get(PORT_ENV_VAR, client.DEFAULT_PORT)
232 parser.add_argument(*port_flags, default=default, type=int, dest='port',
233 action=StoreAndMarkAction,
234 help='port of the servod server. Can also be supplied '
235 'through environment variable ' + PORT_ENV_VAR)
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800236
237
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800238class ServodRCParser(_BaseServodParser):
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800239 """Base class to build Servod parsers to natively handle servorc.
240
241 This class overwrites parse_args & parse_known_args to:
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800242 - handle NAME_ENV_VAR environment variable
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800243 - parse & substitute in the servorc file on matches
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800244 """
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800245
246 def __init__(self, **kwargs):
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800247 super(ServodRCParser, self).__init__(**kwargs)
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800248 self.add_argument('--rcfile', type=str, default=DEFAULT_RC_FILE,
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800249 help='servo description file for multi-servo operation.')
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800250 # name and serialname are both ways to ID a servo device
251 self._id_group = self.add_mutually_exclusive_group()
252 self._id_group.add_argument('-s', '--serialname', default=None, type=str,
253 help='device serialname stored in eeprom.')
254 ServodRCParser.AddRCEnabledNameArg(self._id_group)
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800255
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800256 @staticmethod
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800257 def AddRCEnabledNameArg(parser, name_flags=['-n', '--name']):
258 """Add the name to the argparser.
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800259
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800260 Set the default to environment variable ENV_VAR_NAME if defined
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800261
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800262 Note: while this helper does allow for arbitrary flags for the name
263 variable, the destination is still set to 'name'. It's on the caller to
264 ensure that there is no conflict.
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800265
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800266 Args:
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800267 parser: parser or group to add argument to
268 name_flags: optional, list, if the flags for the name should be different
269 than the default ones.
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800270 """
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800271 # pylint: disable=dangerous-default-value
272 # Having the default flags here simplifies the code logic.
273 default = os.environ.get(NAME_ENV_VAR, '')
274 parser.add_argument(*name_flags, default=default, type=str, dest='name',
275 help='symbolic name of the servo board, '
276 'used as a config shortcut, could also be supplied '
277 'through environment variable ' + NAME_ENV_VAR)
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800278
279 @staticmethod
280 def PostProcessRCElements(options, rcpath=None, logger=logging):
281 """Handle 'name' in |options| by substituting it with the intended config.
282
283 This replaces the name option in the options with the intended serialname
284 for that name if one can be found. If a board file is also specified in the
285 rc file it appends that to the options too, which can be ignored if not
286 needed.
287
288 Note: this function changes the content of options.
289
290 Args:
291 options: argparse Namespace of options to process.
292 rcpath: optional rcfile path if it's not stored under options.rcfile
293 logger: logger instance to use
294
295 Returns:
296 Reference back to the same options passed in.
297
298 Raises:
299 ServodParserError: if -n/--name and -s/--serialname both defined
300 ServodParserError: if name in options doesn't show up in servodrc
301 """
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800302 if not rcpath:
303 rcpath = options.rcfile
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800304 rcd = ServodRCParser.ParseRC(rcpath, logger=logger)
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800305 rc = None
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800306 if not options.serialname and options.name:
307 # |name| can be set through the commandline or through an environment
308 # variable. If it's set through the commandline, serialname cannot have
309 # been set. However, if serialname is set and name is also set (through
310 # the environment variable) name gets ignored.
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800311 if options.name not in rcd:
312 raise ServodParserError('Name %r not in rc at %r' % (options.name,
313 rcpath))
314 rc = rcd[options.name]
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800315 # For an rc to exist, 'sn' has to be a part of it
316 setattr(options, 'serialname', rc['sn'])
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800317 elif options.serialname:
318 # srcs meaning serialname runtime configurations (rcs).
Ruben Rodriguez Buchillon24c0ad82020-03-27 19:12:11 -0700319 srcs = [(name, rc) for name, rc in rcd.items() if
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800320 rc['sn'] == options.serialname]
321 if srcs:
322 logger.info('Found servodrc entry %r for serialname %r. Using it.',
323 srcs[0][0], options.serialname)
324 rc = srcs[0][1]
325 if rc:
326 for elem in ['board', 'model']:
327 # Unlike serialname explicit overwrites of board and model in the
328 # cmdline are fine as the name flag is still useful to refer to a
329 # serialname.
330 if elem in rc and hasattr(options, elem):
331 if not getattr(options, elem):
332 logger.info('Setting %r to %r in the options as indicated by '
333 'servodrc file.', elem, rc[elem])
334 setattr(options, elem, rc[elem])
335 else:
336 if getattr(options, elem) != rc[elem]:
337 logger.warning('Ignoring rc configured %r name %r for servo %r. '
338 'Option already defined on the command line as %r',
339 elem, rc[elem], rc['sn'], getattr(options, elem))
340 return options
341
342 def parse_known_args(self, args=None, namespace=None):
343 """Overwrite from Argumentparser to handle servo rc.
344
345 Note: this also overwrites parse_args as parse_args just calls
346 parse_known_args and throws an error if there's anything inside of
347 xtra.
348
349 Args:
350 args: list of cmdline elements
351 namespace: namespace to place the results into
352
353 Returns:
354 tuple (options, xtra) the result from parsing known args
355 """
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800356 opts, xtra = _BaseServodParser.parse_known_args(self, args=args,
357 namespace=namespace)
358 opts = ServodRCParser.PostProcessRCElements(opts, logger=self._logger)
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800359 return (opts, xtra)
360
361 @staticmethod
362 def ParseRC(rc_file, logger=logging):
363 """Parse servodrc configuration file.
364
365 The format of the configuration file is described above in comments to
366 DEFAULT_RC_FILE. If the file is not found or is mis-formatted, a warning is
367 printed but the program tries to continue.
368
369 Args:
370 rc_file: a string, name of the file storing the configuration
371 logger: logger instance to use
372
373 Returns:
374 a dictionary, where keys are symbolic servo names, and values are
375 dictionaries representing servo parameters read from the config file,
376 keyed by strings 'sn' (for serial number), 'port', 'board', and 'model'.
377 """
378
379 if not os.path.isfile(rc_file):
380 return {}
381 rcd = {} # Dictionary representing the rc file contents.
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800382 attributes = ['name', 'sn', 'port', 'board', 'model']
383 # These attributes have to be defined for a line to be valid.
384 required_attributes = ['name', 'sn']
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800385 with open(rc_file) as f:
386 for rc_line in f:
387 line = rc_line.split('#')[0].strip()
388 if not line:
389 continue
390 elts = [x.strip() for x in line.split(',')]
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800391 if len(elts) < len(required_attributes):
392 logger.warning('ignoring rc line %r. Not all required '
393 'attributes defined %r.', rc_line.rstrip(),
394 required_attributes)
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800395 continue
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800396 # Initialize all to None that are not in elts
397 line_content = dict(zip(attributes, elts + [None] * len(attributes)))
398 # All required attributes are defined. Store the entry.
399 name = line_content.pop('name')
400 if len(elts) > len(attributes):
401 extra_info = elts[len(attributes):]
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800402 logger.warning('discarding %r for for %r', ', '.join(extra_info),
403 name)
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800404 rcd[name] = line_content
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800405 return rcd
406
407
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800408class ServodClientParser(ServodRCParser):
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800409 """Parser to use for servod client cmdline tools.
410
411 This parser adds servoscratch serialname<>port conversion to allow
412 for servod client cmdline tools to address servod using a servo device's
413 serialname as well.
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800414 """
415
Ruben Rodriguez Buchillon10a3a9a2020-02-27 16:02:54 -0800416 def __init__(self, scratch=utils.scratch.SERVO_SCRATCH_DIR, **kwargs):
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800417 """Create a ServodRCParser that has the BaseServodParser args.
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800418
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800419 (for testing) pass a scratch directory instead of the global default.
420
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800421 Args:
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800422 scratch: scratch directory to use
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800423 **kwargs: keyword arguments forwarded to _BaseServodParser
424 """
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800425 # BaseServodParser is used here to get the common arguments. Later,
426 # the ServodClientParser adds port itself, because from a client perspective
427 # there is mutual exclusion between --port/--serialname/--name as they serve
428 # one purpose: to identify an instance.
429 self._scratchdir = scratch
430 base_parser = BaseServodParser(add_port=False, add_help=False)
431 if 'parents' not in kwargs:
432 kwargs['parents'] = []
433 kwargs['parents'].append(base_parser)
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800434 super(ServodClientParser, self).__init__(**kwargs)
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800435 # Add --port to the |_id_group| to ensure exclusion with name and
436 # serialname.
437 BaseServodParser.AddRCEnabledPortArg(self._id_group)
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800438
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800439 def _MapSNToPort(self, opts):
440 """Helper to map the serialname in opts to the port its running on.
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800441
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800442 Args:
443 opts: ArgumentParser Namespace after parsing.
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800444
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800445 Returns:
446 opts: reference back to passed in opts
447
448 Raises:
449 Forces a program exit if |opts.serialname| is not found in the servo
450 scratch
451 """
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800452 # Passing None here uses the default production logic while passing any
453 # other directory can be used for testing. No need to check whether
454 # |self._scratchdir| is None.
Ruben Rodriguez Buchillon479bd242020-02-14 18:58:30 -0800455 scratch = utils.scratch.Scratch(self._scratchdir)
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800456 try:
457 entry = scratch.FindById(opts.serialname)
Ruben Rodriguez Buchillon479bd242020-02-14 18:58:30 -0800458 except utils.scratch.ScratchError:
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800459 self.error('No servod instance running for device with serialname: %r' %
460 opts.serialname)
461 opts.port = int(entry['port'])
462 return opts
463
464 def parse_known_args(self, args=None, namespace=None):
465 """Overwrite from Argumentparser to handle servo scratch logic.
466
467 If port is not defined and serialname is defined, and serialname has
468 no scratch entry, this will raise an error & terminate the program.
469
470 If there was neither a serialname nor a port, set the port to the
471 default port.
472
473 Note: this also overwrites parse_args as parse_args just calls
474 parse_known_args and throws an error if there's anything inside of
475 xtra.
476
477 Args:
478 args: list of cmdline elements
479 namespace: namespace to place the results into
480
481 Returns:
482 tuple (opts, xtra) the result from parsing known args
483 """
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800484 opts, xtra = _BaseServodParser.parse_known_args(self, args=args,
485 namespace=namespace)
486 opts = ServodRCParser.PostProcessRCElements(opts, logger=self._logger)
487 if opts.serialname:
488 # If serialname is set, this means that either serialname or name was used
489 # to find it, and therefore port cannot have been set by the user due to
490 # mutual exclusion.
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800491 opts = self._MapSNToPort(opts)
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800492 return (opts, xtra)