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