blob: 6d0cfeea2894beafdd88df2eb6cbe121bd9e3d9e [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 Buchillonbb65ba82020-03-20 13:27:43 -0700202 if 'version' not in kwargs:
203 kwargs['version'] = VERSION
Ruben Rodriguez Buchillon0902f092018-09-19 15:03:17 +0800204 super(BaseServodParser, self).__init__(**kwargs)
Ruben Rodriguez Buchillonbb65ba82020-03-20 13:27:43 -0700205 self.add_argument('--sversion', action='version', version=SVERSION)
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800206 self.add_argument('-d', '--debug', action='store_true', default=False,
207 help='enable debug messages')
208 self.add_argument('--host', default='localhost', type=str,
209 help='hostname of the servod server.')
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800210 if add_port:
211 BaseServodParser.AddRCEnabledPortArg(self)
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800212
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800213 @staticmethod
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800214 def AddRCEnabledPortArg(parser, port_flags=['-p', '--port']):
215 """Add the port to the argparser.
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800216
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800217 Set the default to environment variable ENV_PORT_NAME if defined
218
219 Note: while this helper does allow for arbitrary flags for the port
220 variable, the destination is still set to 'port'. It's on the caller to
221 ensure that there is no conflict.
Ruben Rodriguez Buchillon0902f092018-09-19 15:03:17 +0800222
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800223 Args:
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800224 parser: parser or group to add argument to
225 port_flags: optional, list, if the flags for the port should be different
226 than the default ones.
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800227 """
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800228 # pylint: disable=dangerous-default-value
229 # Having the default flags here simplifies the code logic.
230 default = os.environ.get(PORT_ENV_VAR, client.DEFAULT_PORT)
231 parser.add_argument(*port_flags, default=default, type=int, dest='port',
232 action=StoreAndMarkAction,
233 help='port of the servod server. Can also be supplied '
234 'through environment variable ' + PORT_ENV_VAR)
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800235
236
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800237class ServodRCParser(_BaseServodParser):
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800238 """Base class to build Servod parsers to natively handle servorc.
239
240 This class overwrites parse_args & parse_known_args to:
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800241 - handle NAME_ENV_VAR environment variable
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800242 - parse & substitute in the servorc file on matches
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800243 """
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800244
245 def __init__(self, **kwargs):
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800246 super(ServodRCParser, self).__init__(**kwargs)
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800247 self.add_argument('--rcfile', type=str, default=DEFAULT_RC_FILE,
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800248 help='servo description file for multi-servo operation.')
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800249 # name and serialname are both ways to ID a servo device
250 self._id_group = self.add_mutually_exclusive_group()
251 self._id_group.add_argument('-s', '--serialname', default=None, type=str,
252 help='device serialname stored in eeprom.')
253 ServodRCParser.AddRCEnabledNameArg(self._id_group)
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800254
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800255 @staticmethod
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800256 def AddRCEnabledNameArg(parser, name_flags=['-n', '--name']):
257 """Add the name to the argparser.
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800258
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800259 Set the default to environment variable ENV_VAR_NAME if defined
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800260
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800261 Note: while this helper does allow for arbitrary flags for the name
262 variable, the destination is still set to 'name'. It's on the caller to
263 ensure that there is no conflict.
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800264
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800265 Args:
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800266 parser: parser or group to add argument to
267 name_flags: optional, list, if the flags for the name should be different
268 than the default ones.
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800269 """
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800270 # pylint: disable=dangerous-default-value
271 # Having the default flags here simplifies the code logic.
272 default = os.environ.get(NAME_ENV_VAR, '')
273 parser.add_argument(*name_flags, default=default, type=str, dest='name',
274 help='symbolic name of the servo board, '
275 'used as a config shortcut, could also be supplied '
276 'through environment variable ' + NAME_ENV_VAR)
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800277
278 @staticmethod
279 def PostProcessRCElements(options, rcpath=None, logger=logging):
280 """Handle 'name' in |options| by substituting it with the intended config.
281
282 This replaces the name option in the options with the intended serialname
283 for that name if one can be found. If a board file is also specified in the
284 rc file it appends that to the options too, which can be ignored if not
285 needed.
286
287 Note: this function changes the content of options.
288
289 Args:
290 options: argparse Namespace of options to process.
291 rcpath: optional rcfile path if it's not stored under options.rcfile
292 logger: logger instance to use
293
294 Returns:
295 Reference back to the same options passed in.
296
297 Raises:
298 ServodParserError: if -n/--name and -s/--serialname both defined
299 ServodParserError: if name in options doesn't show up in servodrc
300 """
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800301 if not rcpath:
302 rcpath = options.rcfile
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800303 rcd = ServodRCParser.ParseRC(rcpath, logger=logger)
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800304 rc = None
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800305 if not options.serialname and options.name:
306 # |name| can be set through the commandline or through an environment
307 # variable. If it's set through the commandline, serialname cannot have
308 # been set. However, if serialname is set and name is also set (through
309 # the environment variable) name gets ignored.
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800310 if options.name not in rcd:
311 raise ServodParserError('Name %r not in rc at %r' % (options.name,
312 rcpath))
313 rc = rcd[options.name]
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800314 # For an rc to exist, 'sn' has to be a part of it
315 setattr(options, 'serialname', rc['sn'])
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800316 elif options.serialname:
317 # srcs meaning serialname runtime configurations (rcs).
318 srcs = [(name, rc) for name, rc in rcd.iteritems() if
319 rc['sn'] == options.serialname]
320 if srcs:
321 logger.info('Found servodrc entry %r for serialname %r. Using it.',
322 srcs[0][0], options.serialname)
323 rc = srcs[0][1]
324 if rc:
325 for elem in ['board', 'model']:
326 # Unlike serialname explicit overwrites of board and model in the
327 # cmdline are fine as the name flag is still useful to refer to a
328 # serialname.
329 if elem in rc and hasattr(options, elem):
330 if not getattr(options, elem):
331 logger.info('Setting %r to %r in the options as indicated by '
332 'servodrc file.', elem, rc[elem])
333 setattr(options, elem, rc[elem])
334 else:
335 if getattr(options, elem) != rc[elem]:
336 logger.warning('Ignoring rc configured %r name %r for servo %r. '
337 'Option already defined on the command line as %r',
338 elem, rc[elem], rc['sn'], getattr(options, elem))
339 return options
340
341 def parse_known_args(self, args=None, namespace=None):
342 """Overwrite from Argumentparser to handle servo rc.
343
344 Note: this also overwrites parse_args as parse_args just calls
345 parse_known_args and throws an error if there's anything inside of
346 xtra.
347
348 Args:
349 args: list of cmdline elements
350 namespace: namespace to place the results into
351
352 Returns:
353 tuple (options, xtra) the result from parsing known args
354 """
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800355 opts, xtra = _BaseServodParser.parse_known_args(self, args=args,
356 namespace=namespace)
357 opts = ServodRCParser.PostProcessRCElements(opts, logger=self._logger)
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800358 return (opts, xtra)
359
360 @staticmethod
361 def ParseRC(rc_file, logger=logging):
362 """Parse servodrc configuration file.
363
364 The format of the configuration file is described above in comments to
365 DEFAULT_RC_FILE. If the file is not found or is mis-formatted, a warning is
366 printed but the program tries to continue.
367
368 Args:
369 rc_file: a string, name of the file storing the configuration
370 logger: logger instance to use
371
372 Returns:
373 a dictionary, where keys are symbolic servo names, and values are
374 dictionaries representing servo parameters read from the config file,
375 keyed by strings 'sn' (for serial number), 'port', 'board', and 'model'.
376 """
377
378 if not os.path.isfile(rc_file):
379 return {}
380 rcd = {} # Dictionary representing the rc file contents.
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800381 attributes = ['name', 'sn', 'port', 'board', 'model']
382 # These attributes have to be defined for a line to be valid.
383 required_attributes = ['name', 'sn']
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800384 with open(rc_file) as f:
385 for rc_line in f:
386 line = rc_line.split('#')[0].strip()
387 if not line:
388 continue
389 elts = [x.strip() for x in line.split(',')]
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800390 if len(elts) < len(required_attributes):
391 logger.warning('ignoring rc line %r. Not all required '
392 'attributes defined %r.', rc_line.rstrip(),
393 required_attributes)
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800394 continue
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800395 # Initialize all to None that are not in elts
396 line_content = dict(zip(attributes, elts + [None] * len(attributes)))
397 # All required attributes are defined. Store the entry.
398 name = line_content.pop('name')
399 if len(elts) > len(attributes):
400 extra_info = elts[len(attributes):]
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800401 logger.warning('discarding %r for for %r', ', '.join(extra_info),
402 name)
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800403 rcd[name] = line_content
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800404 return rcd
405
406
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800407class ServodClientParser(ServodRCParser):
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800408 """Parser to use for servod client cmdline tools.
409
410 This parser adds servoscratch serialname<>port conversion to allow
411 for servod client cmdline tools to address servod using a servo device's
412 serialname as well.
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800413 """
414
Ruben Rodriguez Buchillon10a3a9a2020-02-27 16:02:54 -0800415 def __init__(self, scratch=utils.scratch.SERVO_SCRATCH_DIR, **kwargs):
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800416 """Create a ServodRCParser that has the BaseServodParser args.
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800417
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800418 (for testing) pass a scratch directory instead of the global default.
419
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800420 Args:
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800421 scratch: scratch directory to use
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800422 **kwargs: keyword arguments forwarded to _BaseServodParser
423 """
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800424 # BaseServodParser is used here to get the common arguments. Later,
425 # the ServodClientParser adds port itself, because from a client perspective
426 # there is mutual exclusion between --port/--serialname/--name as they serve
427 # one purpose: to identify an instance.
428 self._scratchdir = scratch
429 base_parser = BaseServodParser(add_port=False, add_help=False)
430 if 'parents' not in kwargs:
431 kwargs['parents'] = []
432 kwargs['parents'].append(base_parser)
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800433 super(ServodClientParser, self).__init__(**kwargs)
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800434 # Add --port to the |_id_group| to ensure exclusion with name and
435 # serialname.
436 BaseServodParser.AddRCEnabledPortArg(self._id_group)
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800437
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800438 def _MapSNToPort(self, opts):
439 """Helper to map the serialname in opts to the port its running on.
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800440
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800441 Args:
442 opts: ArgumentParser Namespace after parsing.
Ruben Rodriguez Buchillon63e38602018-09-19 10:36:58 +0800443
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800444 Returns:
445 opts: reference back to passed in opts
446
447 Raises:
448 Forces a program exit if |opts.serialname| is not found in the servo
449 scratch
450 """
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800451 # Passing None here uses the default production logic while passing any
452 # other directory can be used for testing. No need to check whether
453 # |self._scratchdir| is None.
Ruben Rodriguez Buchillon479bd242020-02-14 18:58:30 -0800454 scratch = utils.scratch.Scratch(self._scratchdir)
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800455 try:
456 entry = scratch.FindById(opts.serialname)
Ruben Rodriguez Buchillon479bd242020-02-14 18:58:30 -0800457 except utils.scratch.ScratchError:
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800458 self.error('No servod instance running for device with serialname: %r' %
459 opts.serialname)
460 opts.port = int(entry['port'])
461 return opts
462
463 def parse_known_args(self, args=None, namespace=None):
464 """Overwrite from Argumentparser to handle servo scratch logic.
465
466 If port is not defined and serialname is defined, and serialname has
467 no scratch entry, this will raise an error & terminate the program.
468
469 If there was neither a serialname nor a port, set the port to the
470 default port.
471
472 Note: this also overwrites parse_args as parse_args just calls
473 parse_known_args and throws an error if there's anything inside of
474 xtra.
475
476 Args:
477 args: list of cmdline elements
478 namespace: namespace to place the results into
479
480 Returns:
481 tuple (opts, xtra) the result from parsing known args
482 """
Ruben Rodriguez Buchillon7be98d72018-09-19 18:03:36 +0800483 opts, xtra = _BaseServodParser.parse_known_args(self, args=args,
484 namespace=namespace)
485 opts = ServodRCParser.PostProcessRCElements(opts, logger=self._logger)
486 if opts.serialname:
487 # If serialname is set, this means that either serialname or name was used
488 # to find it, and therefore port cannot have been set by the user due to
489 # mutual exclusion.
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800490 opts = self._MapSNToPort(opts)
Ruben Rodriguez Buchillon90662392018-09-19 16:28:38 +0800491 return (opts, xtra)