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 keyword
      6 import logging
      7 import re
      8 
      9 import task_loop
     10 import wardmodem_exceptions as wme
     11 
     12 class StateMachine(object):
     13     """
     14     Base class for all state machines in wardmodem.
     15 
     16     All derived objects bundled as part of wardmodem
     17         (1) Reside in state_machines/
     18         (2) Have their own module e.g., my_module
     19         (3) The main state machine class in my_module is called MyModule.
     20 
     21     """
     22 
     23     def __init__(self, state, transceiver, modem_conf):
     24         """
     25         @param state: The GlobalState object shared by all state machines.
     26 
     27         @param transceiver: The ATTransceiver object to interact with.
     28 
     29         @param modem_conf: A modem configuration object that contains
     30                 configuration data for different state machines.
     31 
     32         @raises: SetupException if we attempt to create an instance of a machine
     33         that has not been completely specified (see get_well_known_name).
     34 
     35         """
     36         self._state = state
     37         self._transceiver = transceiver
     38         self._modem_conf = modem_conf
     39 
     40         self._logger = logging.getLogger(__name__)
     41         self._task_loop = task_loop.get_instance()
     42 
     43         self._state_update_tag = 0  # Used to tag logs of async updates to
     44                                     # state.
     45 
     46         # Will raise an exception if this machine should not be instantiated.
     47         self.get_well_known_name()
     48 
     49         # Add all wardmodem response functions used by this machine.
     50         self._add_response_function('wm_response_ok')
     51         self._add_response_function('wm_response_error')
     52         self._add_response_function('wm_response_ring')
     53         self._add_response_function('wm_response_text_only')
     54 
     55 
     56     # ##########################################################################
     57     # Subclasses must override these.
     58     def get_well_known_name(self):
     59         """
     60         A well known name of the completely specified state machine.
     61 
     62         The first derived class that completely specifies some state machine
     63         should implement this function to return the name of the defining module
     64         as a string.
     65 
     66         """
     67         # Do not use self._raise_setup_error because it causes infinite
     68         # recursion.
     69         raise wme.WardModemSetupException(
     70                 'Attempted to get well known name for a state machine that is '
     71                 'not completely specified.')
     72 
     73 
     74     # ##########################################################################
     75     # Protected convenience methods to be used as is by subclasses.
     76 
     77     def _respond(self, response, response_delay_ms=0, *response_args):
     78         """
     79         Respond to the modem after some delay.
     80 
     81         @param reponse: String response. This must be one of the response
     82                 strings recognized by ATTransceiver.
     83 
     84         @param response_delay_ms: Delay in milliseconds after which the response
     85                 should be sent. Type: int.
     86 
     87         @param *response_args: The arguments for the response.
     88 
     89         @requires: response_delay_ms >= 0
     90 
     91         """
     92         assert response_delay_ms >= 0
     93         dbgstr = self._tag_with_name(
     94                 'Will respond with "%s(%s)" after %d ms.' %
     95                 (response, str(response_args), response_delay_ms))
     96         self._logger.debug(dbgstr)
     97         self._task_loop.post_task_after_delay(
     98                 self._transceiver.process_wardmodem_response,
     99                 response_delay_ms,
    100                 response,
    101                 *response_args)
    102 
    103 
    104     def _update_state(self, state_update, state_update_delay_ms=0):
    105         """
    106         Post a (delayed) state update.
    107 
    108         @param state_update: The state update to apply. This is a map {string
    109                 --> state enum} that specifies all the state components to be
    110                 updated.
    111 
    112         @param state_update_delay_ms: Delay in milliseconds after which the
    113                 state update should be applied. Type: int.
    114 
    115         @requires: state_update_delay_ms >= 0
    116 
    117         """
    118         assert state_update_delay_ms >= 0
    119         dbgstr = self._tag_with_name(
    120                 '[tag:%d] Will update state as %s after %d ms.' %
    121                 (self._state_update_tag, str(state_update),
    122                  state_update_delay_ms))
    123         self._logger.debug(dbgstr)
    124         self._task_loop.post_task_after_delay(
    125                 self._update_state_callback,
    126                 state_update_delay_ms,
    127                 state_update,
    128                 self._state_update_tag)
    129         self._state_update_tag += 1
    130 
    131 
    132     def _respond_ok(self):
    133         """ Convenience function to respond when everything is OK. """
    134         self._respond(self.wm_response_ok, response_delay_ms=0)
    135 
    136 
    137     def _respond_error(self):
    138         """ Convenience function to respond when an error occured. """
    139         self._respond(self.wm_response_error, response_delay_ms=0)
    140 
    141 
    142     def _respond_ring(self):
    143         """ Convenience function to respond with RING. """
    144         self._respond(self.wm_response_ring, response_delay_ms=0)
    145 
    146 
    147     def _respond_with_text(self, text):
    148         """ Send back just |text| as the response, without any AT prefix. """
    149         self._respond(self.wm_response_text_only, 0, text)
    150 
    151 
    152     def _add_response_function(self, function):
    153         """
    154         Add a response used by this state machine to send to the ATTransceiver.
    155 
    156         A state machine should register all the responses it will use in its
    157         __init__ function by calling
    158             self._add_response_function('wm_response_dummy')
    159         The response can then be used to respond to the transceiver thus:
    160             self._respond(self.wm_response_dummy)
    161 
    162         @param function: The string function name to add. Must be a valid python
    163                 identifier in lowercase.
    164                 Also, these names are conventionally named matching the re
    165                 'wm_response_([a-z0-9]*[_]?)*'
    166 
    167         @raises: WardModemSetupError if the added response function is ill
    168                 formed.
    169 
    170         """
    171         if not re.match('wm_response_([a-z0-9]*[_]?)*', function) or \
    172            keyword.iskeyword(function):
    173             self._raise_setup_error('Response function name ill-formed: |%s|' %
    174                                     function)
    175         try:
    176             getattr(self, function)
    177             self._raise_setup_error(
    178                     'Attempted to add response function %s which already '
    179                     'exists.' % function)
    180         except AttributeError:  # OK, This is the good case.
    181             setattr(self, function, function)
    182 
    183 
    184     def _raise_setup_error(self, errstring):
    185         """
    186         Log the error and raise WardModemSetupException.
    187 
    188         @param errstring: The error string.
    189 
    190         """
    191         errstring = self._tag_with_name(errstring)
    192         self._logger.error(errstring)
    193         raise wme.WardModemSetupException(errstring)
    194 
    195 
    196     def _raise_runtime_error(self, errstring):
    197         """
    198         Log the error and raise StateMachineException.
    199 
    200         @param errstring: The error string.
    201 
    202         """
    203         errstring = self._tag_with_name(errstring)
    204         self._logger.error(errstring)
    205         raise wme.StateMachineException(errstring)
    206 
    207     def _tag_with_name(self, log_string):
    208         """
    209         If possible, prepend the log string with the well know name of the
    210         object.
    211 
    212         @param log_string: The string to modify.
    213 
    214         @return: The modified string.
    215 
    216         """
    217         name = self.get_well_known_name()
    218         log_string = '[' + name + '] ' + log_string
    219         return log_string
    220 
    221     # ##########################################################################
    222     # Private methods not to be used by subclasses.
    223 
    224     def _update_state_callback(self, state_update, tag):
    225         """
    226         Actually update the state.
    227 
    228         @param state_update: The state update to effect. This is a map {string
    229                 --> state enum} that specifies all the state components to be
    230                 updated.
    231 
    232         @param tag: The tag for this state update.
    233 
    234         @raises: StateMachineException if the state update fails.
    235 
    236         """
    237         dbgstr = self._tag_with_name('[tag:%d] State update applied.' % tag)
    238         self._logger.debug(dbgstr)
    239         for component, value in state_update.iteritems():
    240             self._state[component] = value
    241