Home | History | Annotate | Download | only in wardmodem
      1 # Copyright (c) 2013 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.
      4 
      5 import collections
      6 import inspect
      7 import logging
      8 
      9 import at_channel
     10 import task_loop
     11 import wardmodem_exceptions as wme
     12 
     13 MODEM_RESPONSE_TIMEOUT_MILLISECONDS = 30000
     14 ARG_PLACEHOLDER =  '*'
     15 
     16 class ATTransceiverMode(object):
     17     """
     18     Enum to specify what mode the ATTransceiver is operating in.
     19 
     20     There are three modes. These modes determine how the commands to/from
     21     the modemmanager are routed.
     22         WARDMODEM:  modemmanager interacts with wardmodem alone.
     23         SPLIT_VERIFY: modemmanager commands are sent to both the wardmodem
     24                 and the physical modem on the device. Responses from
     25                 wardmodem are verified against responses from the physical
     26                 modem. In case of a mismatch, wardmodem's response is
     27                 chosen, and a warning is issued.
     28         PASS_THROUGH: modemmanager commands are routed to/from the physical
     29                 modem. Frankly, wardmodem isn't running in this mode.
     30 
     31     """
     32     WARDMODEM = 0
     33     SPLIT_VERIFY = 1
     34     PASS_THROUGH = 2
     35 
     36     MODE_NAME = {
     37             WARDMODEM: 'WARDMODEM',
     38             SPLIT_VERIFY: 'SPLIT_VERIFY',
     39             PASS_THROUGH: 'PASS_THROUGH'
     40     }
     41 
     42 
     43     @classmethod
     44     def to_string(cls, value):
     45         """
     46         A class method to obtain string representation of the enum values.
     47 
     48         @param value: the enum value to stringify.
     49 
     50         """
     51         return "%s.%s" % (cls.__name__, cls.MODE_NAME[value])
     52 
     53 
     54 class ATTransceiver(object):
     55     """
     56     A world facing multiplexer class that orchestrates the communication between
     57     modem manager, the physical modem, and wardmodem back-end.
     58 
     59     """
     60 
     61     def __init__(self, mm_at_port, modem_conf,
     62                  modem_at_port=None):
     63         """
     64         @param mm_at_port: File descriptor for AT port used by modem manager.
     65                 Can not be None.
     66 
     67         @param modem_conf: A ModemConfiguration object containing the
     68                 configuration data for the current modem.
     69 
     70         @param modem_at_port: File descriptor for AT port used by the modem. May
     71                 be None, but that forces ATTransceiverMode.WARDMODEM. Default:
     72                 None.
     73 
     74         """
     75         super(ATTransceiver, self).__init__()
     76         assert mm_at_port is not None
     77 
     78         self._logger = logging.getLogger(__name__)
     79         self._task_loop = task_loop.get_instance()
     80         self._mode = ATTransceiverMode.WARDMODEM
     81         # The time we wait for any particular response from physical modem.
     82         self._modem_response_timeout_milliseconds = (
     83                 MODEM_RESPONSE_TIMEOUT_MILLISECONDS)
     84         # We keep a queue of responses from the wardmodem and physical modem,
     85         # so that we can verify they match.
     86         self._cached_modem_responses = collections.deque()
     87         self._cached_wardmodem_responses = collections.deque()
     88 
     89         # When a wardmodem response has been received but the corresponding
     90         # physical modem response hasn't arrived, we post a task to wait for the
     91         # response.
     92         self._modem_response_wait_task = None
     93 
     94         # We use a map from a set of well known state machine names to actual
     95         # objects to dispatch state machine calls. This allows tests to provide
     96         # alternative implementations of any state machine to wardmodem.
     97         self._state_machines = {}
     98 
     99         # If registered with a non-None machine, the fallback machine is used to
    100         # service all AT commands that are not matched with any other machine.
    101         self._fallback_state_machine = None
    102         self._fallback_machine_function = None
    103 
    104         # Maps an incoming AT command from modemmanager to an internal wardmodem
    105         # action.
    106         self._at_to_wm_action_map = {}
    107         # Maps an internal response from wardmodem to an AT command to be sent
    108         # to modemmanager.
    109         self._wm_response_to_at_map = {}
    110 
    111         # Load mapping between AT commands and wardmodem actions.
    112         self._update_at_to_wm_action_map(modem_conf.base_at_to_wm_action_map)
    113         self._update_at_to_wm_action_map(modem_conf.plugin_at_to_wm_action_map)
    114         self._update_wm_response_to_at_map(
    115                 modem_conf.base_wm_response_to_at_map)
    116         self._update_wm_response_to_at_map(
    117                 modem_conf.plugin_wm_response_to_at_map)
    118         self._logger.debug('Finished loading AT --> wardmodem configuration.')
    119         self._logger.debug(self._at_to_wm_action_map)
    120         self._logger.debug('Finished loading wardmodem --> AT configuration.')
    121         self._logger.debug(self._wm_response_to_at_map)
    122 
    123         # Initialize channels -- let the session begin.
    124         if modem_at_port is not None:
    125             self._modem_channel = at_channel.ATChannel(
    126                     self._process_modem_at_command,
    127                     modem_at_port,
    128                     'modem_primary_channel')
    129             self._modem_channel.at_prefix = modem_conf.mm_to_modem_at_prefix
    130             self._modem_channel.at_suffix = modem_conf.mm_to_modem_at_suffix
    131         else:
    132             self._modem_channel = None
    133 
    134         self._mm_channel = at_channel.ATChannel(self._process_mm_at_command,
    135                                                 mm_at_port,
    136                                                 'mm_primary_channel')
    137         self._mm_channel.at_prefix = modem_conf.modem_to_mm_at_prefix
    138         self._mm_channel.at_suffix = modem_conf.modem_to_mm_at_suffix
    139 
    140 
    141     # Verification failure reasons
    142     VERIFICATION_FAILED_MISMATCH = 1
    143     VERIFICATION_FAILED_TIME_OUT = 2
    144 
    145 
    146     @property
    147     def mode(self):
    148         """
    149         ATTranscieverMode value. Determines how commands are routed.
    150 
    151         @see ATTransceiverMode
    152 
    153         """
    154         return self._mode
    155 
    156 
    157     @mode.setter
    158     def mode(self, value):
    159         """
    160         Set mode.
    161 
    162         @param value: The value to set. Type: ATTransceiverMode.
    163 
    164         """
    165         if value != ATTransceiverMode.WARDMODEM and self._modem_channel is None:
    166             self._logger.warning(
    167                     'Can not switch to %s mode. No modem port provided.',
    168                     ATTransceiverMode.to_string(value))
    169             return
    170         self._logger.info('Set mode to %s',
    171                           ATTransceiverMode.to_string(value))
    172         self._mode = value
    173 
    174 
    175     def get_state_machine(self, well_known_name):
    176         """
    177         Get the registered state machine for the given well known name.
    178 
    179         @param well_known_name: The name of the desired machine.
    180 
    181         @return: The machine. None if not found.
    182 
    183         """
    184         return self._state_machines.get(well_known_name, None)
    185 
    186 
    187     def register_state_machine(self, state_machine):
    188         """
    189         Register a new state machine.
    190 
    191         We maintain a map from the well known name of the state machine to the
    192         object. Any older object mapped to the same name will be replaced.
    193 
    194         @param state_machine: [StateMachine object] The state machine
    195                 object to be used to dispatch calls.
    196 
    197         """
    198         state_machine_name = state_machine.get_well_known_name()
    199         self._state_machines[state_machine_name] = state_machine
    200 
    201 
    202     def register_fallback_state_machine(self, state_machine_name, function):
    203         """
    204         Register the fallback state machine to forward AT commands to.
    205 
    206         If this machine is registered, all AT commands for which no matching
    207         rule is found will result in the call |state_machine|.|function|(at).
    208         where |at| is the actual AT command that could not be matched.
    209 
    210         @param state_machine_name: Well known name of the machine to fallback on
    211                 if no machine matches an incoming AT command.
    212 
    213         @param function: The function in |state_machine| to call.
    214 
    215         """
    216         if state_machine_name not in self._state_machines:
    217             self._setup_error('Machine %s, set as fallback, has not been '
    218                               'registered. ' % state_machine_name)
    219         self._fallback_state_machine = state_machine_name
    220         self._fallback_machine_function = function
    221 
    222 
    223     def process_wardmodem_response(self, response, *args):
    224         """
    225         Convert responses from the wardmodem into AT commands and send them to
    226         modemmanager.
    227 
    228         @param response: wardmodem response to be translated to AT response to
    229                 the modem manager.
    230 
    231         @param *args: arguments to the wardmodem response.
    232 
    233         @raises: ATTransceiverError if the response can not be translated into
    234                 an AT command.
    235 
    236         """
    237         self._logger.debug('Processing wardmodem response %s%s',
    238                            response, str(args) if args else '')
    239         if response not in self._wm_response_to_at_map:
    240             self._runtime_error('Unknown wardmodem response |%s|' % response)
    241         at_response = self._construct_at_response(
    242                 self._wm_response_to_at_map[response], *args)
    243         self._process_wardmodem_at_command(at_response)
    244 
    245     # ##########################################################################
    246     # Callbacks -- These are the functions that process events from the
    247     # ATChannel or the TaskLoop. These functions are either
    248     #   (1) set as callbacks in the ATChannel, or
    249     #   (2) called internally to process the AT command to/from the TaskLoop.
    250 
    251     def _process_modem_at_command(self, command):
    252         """
    253         Callback called by the physical modem channel when an AT response is
    254         received.
    255 
    256         @param command: AT command sent by the physical modem.
    257 
    258         """
    259         assert self.mode != ATTransceiverMode.WARDMODEM
    260         self._logger.debug('Command {modem ==> []}: |%s|', command)
    261         if self.mode == ATTransceiverMode.PASS_THROUGH:
    262             self._logger.debug('Command {[] ==> mm}: |%s|' , command)
    263             self._mm_channel.send(command)
    264         else:
    265             self._cached_modem_responses.append(command)
    266             self._verify_and_send_mm_commands()
    267 
    268 
    269     def _process_mm_at_command(self, command):
    270         """
    271         Callback called by the modem manager channel when an AT command is
    272         received.
    273 
    274         @param command: AT command sent by modem manager.
    275 
    276         """
    277         self._logger.debug('Command {mm ==> []}: |%s|', command)
    278         if(self.mode == ATTransceiverMode.PASS_THROUGH or
    279            self.mode == ATTransceiverMode.SPLIT_VERIFY):
    280             self._logger.debug('Command {[] ==> modem}: |%s|', command)
    281             self._modem_channel.send(command)
    282         if(self.mode == ATTransceiverMode.WARDMODEM or
    283            self.mode == ATTransceiverMode.SPLIT_VERIFY):
    284             self._logger.debug('Command {[] ==> wardmodem}: |%s|', command)
    285             self._post_wardmodem_request(command)
    286 
    287 
    288     def _process_wardmodem_at_command(self, command):
    289         """
    290         Function called to process an AT command response of wardmodem.
    291 
    292         This function is called after the response from the task loop has been
    293         converted to an AT command.
    294 
    295         @param command: The AT command response of wardmodem.
    296 
    297         """
    298         assert self.mode != ATTransceiverMode.PASS_THROUGH
    299         self._logger.debug('Command {wardmodem ==> []: |%s|', command)
    300         if self.mode == ATTransceiverMode.WARDMODEM:
    301             self._logger.debug('Command {[] ==> mm}: |%s|', command)
    302             self._mm_channel.send(command)
    303         else:
    304             self._cached_wardmodem_responses.append(command)
    305             self._verify_and_send_mm_commands()
    306 
    307 
    308     def _post_wardmodem_request(self, command):
    309         """
    310         For an AT command, find out the action to be taken on wardmodem and post
    311         the action.
    312 
    313         @param command: AT command for which a request must be posted to
    314                 wardmodem.
    315 
    316         @raises: ATTransceiverException if no valid action exists for the given
    317                 AT command.
    318 
    319         """
    320         action = self._find_wardmodem_action_for_at(command)
    321         state_machine_name, function_name, args = action
    322         try:
    323             state_machine = self._state_machines[state_machine_name]
    324         except KeyError:
    325             self._runtime_error(
    326                     'Malformed action registered for AT command -- Unknown '
    327                     'state machine. AT command: |%s|. Action: |%s|' %
    328                     (command, action))
    329         try:
    330             function = getattr(state_machine, function_name)
    331         except AttributeError:
    332             self._runtime_error(
    333                     'Malformed action registered for AT command -- Unkonwn '
    334                     'function name. AT command: |%s|. Action: |%s|. Object '
    335                     'dictionary: %s.' % (command, action, dir(state_machine)))
    336 
    337         self._task_loop.post_task(
    338                 self._execute_state_machine_function, command, action, function,
    339                 *args)
    340 
    341     # ##########################################################################
    342     # Helper functions
    343 
    344     def _execute_state_machine_function(self, at_command, action, function,
    345                                         *args):
    346         """
    347         A thin wrapper to execute state_machine.function(args). Instead of
    348         posting the call directly, this method is posted for better error
    349         reporting in case of failure.
    350 
    351         @param at_command: The AT command for which this function was called.
    352 
    353         @param action: The matching wardmodem action which led to this function
    354                 call.
    355 
    356         @param function: The function to call.
    357 
    358         @param *args: Arguments to be passed to function.
    359 
    360         """
    361         try:
    362             function(*args)
    363         except TypeError as e:
    364             self._logger.error(
    365                     'Possible malformed action registered for AT command -- '
    366                     'Incorrect arguments. AT command: |%s|. Action: |%s|. '
    367                     'Expected function signature: %s. '
    368                     'Original error raised: |%s|',
    369                     at_command, action, inspect.getargspec(function), str(e))
    370             # use 'raise' here to preserve the original backtrace.
    371             raise
    372 
    373 
    374     def _update_at_to_wm_action_map(self, raw_map):
    375         """
    376         Update the dictionary that maps AT commands and their arguments to the
    377         action to be taken by wardmodem.
    378 
    379         The internal map updated is
    380             {at_command, {(arg1, arg2, ...), (state_machine_name,
    381                                               function,
    382                                               (idx1, idx2, ...))}}
    383         Here,
    384             - at_command [string] is the AT Command received,
    385             - (arg1, arg2, ...) [tuple of string] is possibly empty, and
    386               specifies the arguments that need to be matched. It may contain
    387               the special symbol '*' to mean ignore that argument while
    388               matching.
    389             - state_machine_name [string] is name of a state machine in the
    390               state machine map.
    391             - function [string] is a function exported by the state machine
    392               mapped to by state_machine_name
    393             - (idx1, idx2, ...) [tuple of int] lists the (string) arguments that
    394               should be passed on from the AT command to the called function.
    395 
    396         @param raw_map: The raw map from AT command to function read in from the
    397                 configuration file. For the format of this map, see the comment
    398                 at the head of a configuration file.
    399 
    400         @raises WardModemSetupException if raw_map was not well-formed, and the
    401                 update failed. Absolutely no guarantees about the state of the
    402                 map if the update fails.
    403 
    404         """
    405         for atcom in raw_map:
    406             try:
    407                 at, args = self._parse_at_command(atcom)
    408             except wme.ATTransceiverException as e:
    409                 self._setup_error(e.args)
    410             action = self._sanitize_wardmodem_action(raw_map[atcom])
    411 
    412             if at not in self._at_to_wm_action_map:
    413                 self._at_to_wm_action_map[at] = {}
    414             if args in self._at_to_wm_action_map[at]:
    415                 self._logger.debug('Updated at_to_wm_action_map: '
    416                                    '|%s(%s): [%s --> %s]|',
    417                                    at, args,
    418                                    str(self._at_to_wm_action_map[at][args]),
    419                                    str(action))
    420             else:
    421                 self._logger.debug('Added to at_to_wm_action_map: |%s(%s): %s|',
    422                                    at, args, str(action))
    423             self._at_to_wm_action_map[at][args] = action
    424 
    425 
    426     def _update_wm_response_to_at_map(self, raw_map):
    427         """
    428         Update the dictionary that maps wardmodem responses to AT commands.
    429 
    430         The internal map updated is of the same form as raw_map:
    431           {response_function: at_response}
    432         where both response_function and at_response are of type string.
    433         at_resposne may contain special placeholder charachters '*'.
    434 
    435         @param raw_map: The map read in from the configuration file.
    436 
    437         """
    438         for response_function, at_response in raw_map.iteritems():
    439             if response_function in self._wm_response_to_at_map:
    440                 self._logger.debug(
    441                         'Updated wm_response_to_at_map: |%s: [%s --> %s]|',
    442                         response_function,
    443                         self._wm_response_to_at_map[response_function],
    444                         at_response)
    445             else:
    446                 self._logger.debug(
    447                         'Added to wm_response_to_at_map: |%s: %s|',
    448                         response_function, at_response)
    449             self._wm_response_to_at_map[response_function] = at_response
    450 
    451 
    452     def _sanitize_wardmodem_action(self, action):
    453         """
    454         Test that the action specified in the AT command --> wardmodem action
    455         map is sane and normalize to simplify handling later.
    456 
    457         Currently, this only checks that the action consists of tuples of the
    458         right size / type. It might make sense to make this check a lot stricter
    459         so that ill-formed configuration files are caught early.
    460 
    461         Returns the normalized form: 3-tuple with the last item being a tuple of
    462         integers.
    463 
    464         @param action: The action tuple to check.
    465 
    466         @return action: Sanitized action tuple. Normalized form is (string,
    467         string, (int*)).
    468 
    469         @raises: WardModemSetupException if action is ill-formed.
    470 
    471         """
    472         errstr = ('Ill formed action |%s|. Action must be of the form: '
    473                   '(state_machine_name, function_name, (index_tuple)) '
    474                   'Here, index_tuple is a tuple of integers.' % str(action))
    475         sanitized_action = []
    476         if type(action) is not tuple:
    477             self._setup_error(errstr)
    478         if len(action) != 2 and len(action) != 3:
    479             self._setup_error(errstr)
    480         if type(action[0]) != str or type(action[1]) != str:
    481             self._setup_error(errstr)
    482         sanitized_action.append(action[0])
    483         sanitized_action.append(action[1])
    484         if len(action) != 3:
    485             sanitized_action.append(())
    486         else:
    487             if type(action[2]) == tuple:
    488                 for idx in action[2]:
    489                     if type(idx) != int:
    490                         self._setup_error(errstr)
    491                 sanitized_action.append(action[2])
    492             else:
    493                 if type(action[2]) != int:
    494                     self._setup_error(errstr)
    495                 sanitized_action.append((action[2],))
    496         return tuple(sanitized_action)
    497 
    498 
    499     def _parse_at_command(self, atcom):
    500         """
    501         Parse an AT command into the command and its arguments
    502 
    503         Examples:
    504         'AT?' --> ('AT?', ())
    505         'AT+XX' --> ('AT+XX', ())
    506         'AT%SCF=1,2' --> ('AT%SCF=', ('1', '2'))
    507         'ATX=*' --> ('ATX=', ('*',))
    508 
    509         @param atcom: [string] the AT command to parse
    510 
    511         @return: [(string, (string))] A tuple of the AT command proper and a
    512         tuple of arguments. If no arguments are present, an empty argument
    513         tuple is included.
    514 
    515         @raises ATTransceiverError if atcom is not well-formed.
    516 
    517         """
    518         parts = atcom.split('=')
    519         if len(parts) > 2:
    520             self._runtime_error('Parsing error: |%s|' % atcom)
    521         if len(parts) == 1:
    522             return (atcom, ())
    523         # Note: Include the trailing '=' in the AT commmand.
    524         at = parts[0] + '='
    525         if parts[1] == '':
    526             # This was a command of the form 'ATXXX='.
    527             # Treat this as having no arguments, instead of a single ''
    528             # argument.
    529             return (at, ())
    530         else:
    531             return (at, tuple(parts[1].split(',')))
    532 
    533 
    534     def _find_wardmodem_action_for_at(self, atcom):
    535         """
    536         For the given AT command, find the appropriate action from wardmodem.
    537         This will attempt to find a rule matching |atcom|. If that fails, and if
    538         |_fallback_state_machine| exists, the default action from this machine
    539         is returned.
    540 
    541         @param atcom: The AT command to find action for. Type: str.
    542 
    543         @return: Returns the tuple of (state_machine_name, function,
    544                 (arguments,)) for the corresponding action. The action to be
    545                 taken is roughly
    546                     state_machine.function(arguments)
    547                 Type: (string, string, (string,))
    548 
    549         @raises: ATTransceiverException if the at command is ill-formed or we
    550                 don't have a corresponding action.
    551 
    552         """
    553         try:
    554             at, args = self._parse_at_command(atcom)
    555         except wme.ATTransceiverException as e:
    556             self._runtime_error(
    557                     'Ill formed AT command received. %s' % str(e.args))
    558         if at not in self._at_to_wm_action_map:
    559             if self._fallback_state_machine:
    560                 return (self._fallback_state_machine,
    561                         self._fallback_machine_function,
    562                         (atcom,))
    563             self._runtime_error('Unknown AT command: |%s|' % atcom)
    564 
    565         for candidate_args in self._at_to_wm_action_map[at]:
    566             candidate_action = self._at_to_wm_action_map[at][candidate_args]
    567             if self._args_match(args, candidate_args):
    568                 # Found corresponding entry, now replace the indices of the
    569                 # arguments in the action with actual arguments.
    570                 machine, function, idxs = candidate_action
    571                 fargs = []
    572                 for idx in idxs:
    573                     fargs.append(args[idx])
    574                 return machine, function, tuple(fargs)
    575 
    576         if self._fallback_state_machine:
    577             return (self._fallback_state_machine,
    578                     self._fallback_machine_function,
    579                     (atcom,))
    580         self._runtime_error('Unhandled arguments: |%s|' % atcom)
    581 
    582 
    583     def _args_match(self, args, matches):
    584         """
    585         Check whether args are captured by regexp.
    586 
    587         @param args: A tuple of strings, the arguments to check for inclusion.
    588 
    589         @param matches: A similar tuple, but may contain the wild-card '*'.
    590 
    591         @return True if args is represented by regexp, False otherwise.
    592 
    593         """
    594         if len(args) != len(matches):
    595             return False
    596         for i in range(len(args)):
    597             arg = args[i]
    598             match = matches[i]
    599             if match == ARG_PLACEHOLDER:
    600                 return True
    601             if arg != match:
    602                 return False
    603         return True
    604 
    605     def _construct_at_response(self, raw_at, *args):
    606         """
    607         Replace palceholders in an AT command template with actual arguments.
    608 
    609         @param raw_at: An AT command with '*' placeholders where arguments
    610                 should be provided.
    611 
    612         @param *args: Arguments to fill in the placeholders in |raw_at|.
    613 
    614         @return: AT command with placeholders replaced by arguments.
    615 
    616         @raises: ATTransceiverException if the number of arguments does not
    617                 match the number of placeholders.
    618 
    619         """
    620         parts = raw_at.split(ARG_PLACEHOLDER)
    621         if len(args) < (len(parts) - 1):
    622             self._runtime_error(
    623                     'Failed to construct AT response from |%s|. Expected %d '
    624                     'arguments, found %d.' %
    625                     (raw_at, len(parts) - 1, len(args)))
    626         if len(args) > (len(parts) - 1):
    627             self._logger.warning(
    628                     'Number of arguments in wardmodem response greater than '
    629                     'expected. Some of the arguments from %s will not be used '
    630                     'in the reconstruction of %s', str(args), raw_at)
    631 
    632         ret = []
    633         for i in range(len(parts) - 1):
    634             ret += parts[i]
    635             ret += str(args[i])
    636         ret += parts[len(parts) - 1]
    637         return ''.join(ret)
    638 
    639 
    640     def _verify_and_send_mm_commands(self):
    641         """
    642         While there are corresponding responses from wardmodem and physical
    643         modem, verify that they match and respond to modem manager.
    644 
    645         """
    646         if not self._cached_wardmodem_responses:
    647             return
    648         elif not self._cached_modem_responses:
    649             if self._modem_response_wait_task is not None:
    650                 return
    651             self._modem_response_wait_task = (
    652                     self._task_loop.post_task_after_delay(
    653                             self._modem_response_timed_out,
    654                             self._modem_response_timeout_milliseconds))
    655         else:
    656             if self._modem_response_wait_task is not None:
    657                 self._task_loop.cancel_posted_task(
    658                         self._modem_response_wait_task)
    659                 self._modem_response_wait_task = None
    660             self._verify_and_send_mm_command(
    661                     self._cached_modem_responses.popleft(),
    662                     self._cached_wardmodem_responses.popleft())
    663             self._verify_and_send_mm_commands()
    664 
    665 
    666     def _verify_and_send_mm_command(self, modem_response, wardmodem_response):
    667         """
    668         Verify that the two AT commands match and respond to modem manager.
    669 
    670         @param modem_response: AT command response of the physical modem.
    671 
    672         @param wardmodem_response: AT command response of wardmodem.
    673 
    674         """
    675         # TODO(pprabhu) This can not handle unsolicited commands yet.
    676         # Unsolicited commands from either of the modems will push the lists out
    677         # of sync.
    678         if wardmodem_response != modem_response:
    679             self._logger.warning('Response verification failed.')
    680             self._logger.warning('modem response: |%s|', modem_response)
    681             self._logger.warning('wardmodem response: |%s|', wardmodem_response)
    682             self._logger.warning('wardmodem response takes precedence.')
    683             self._report_verification_failure(
    684                     self.VERIFICATION_FAILED_MISMATCH,
    685                     modem_response,
    686                     wardmodem_response)
    687         self._logger.debug('Command {[] ==> mm}: |%s|' , wardmodem_response)
    688         self._mm_channel.send(wardmodem_response)
    689 
    690 
    691     def _modem_response_timed_out(self):
    692         """
    693         Callback called when we time out waiting for physical modem response for
    694         some wardmodem response. Can't do much -- log physical modem failure and
    695         forward wardmodem response anyway.
    696 
    697         """
    698         assert (not self._cached_modem_responses and
    699                 self._cached_wardmodem_responses)
    700         wardmodem_response = self._cached_wardmodem_responses.popleft()
    701         self._logger.warning('modem response timed out. '
    702                              'Forwarding wardmodem response |%s| anyway.',
    703                              wardmodem_response)
    704         self._logger.debug('Command {[] ==> mm}: |%s|' , wardmodem_response)
    705         self._report_verification_failure(
    706                 self.VERIFICATION_FAILED_TIME_OUT,
    707                 None,
    708                 wardmodem_response)
    709         self._mm_channel.send(wardmodem_response)
    710         self._modem_response_wait_task = None
    711         self._verify_and_send_mm_commands()
    712 
    713 
    714     def _report_verification_failure(self, failure, modem_response,
    715                                      wardmodem_response):
    716         """
    717         Failure to verify the wardmodem response will call this non-public
    718         method.
    719 
    720         At present, it is only used by unittests to detect failure.
    721 
    722         @param failure: The cause of failure. Must be one of
    723                 VERIFICATION_FAILED_MISMATCH or VERIFICATION_FAILED_TIME_OUT.
    724 
    725         @param modem_response: The received modem response (if any).
    726 
    727         @param wardmodem_response: The received wardmodem response.
    728 
    729         """
    730         pass
    731 
    732 
    733     def _runtime_error(self, error_message):
    734         """
    735         Log the message at error level and raise ATTransceiverException.
    736 
    737         @param error_message: The error message.
    738 
    739         @raises: ATTransceiverException.
    740 
    741         """
    742         self._logger.error(error_message)
    743         raise wme.ATTransceiverException(error_message)
    744 
    745 
    746     def _setup_error(self, error_message):
    747         """
    748         Log the message at error level and raise WardModemSetupException.
    749 
    750         @param error_message: The error message.
    751 
    752         @raises: WardModemSetupException.
    753 
    754         """
    755         self._logger.error(error_message)
    756         raise wme.WardModemSetupException(error_message)
    757