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 keyword 7 import logging 8 import re 9 10 import wardmodem_exceptions as wme 11 12 class GlobalStateSkeleton(collections.MutableMapping): 13 """ 14 A skeleton to create global state. 15 16 The global state should be an object of a derived class. 17 18 To declare a new state component called dummy_var, with allowed values 19 DUMMY_VAR_WOOF and DUMMY_VAR_POOF, add a call to 20 self._add_state_component('dummy_var', 21 ['DUMMY_VAR_WOOF', 'DUMMY_VAR_POOF']) 22 in __init__ of the derived class. 23 24 Then any state machine that has the global state object, say gstate, can 25 use the state component, viz, 26 To read: my_state_has_val = gstate['dummy_var'] 27 my_state_has_val = gstate[gstate.dummy_var] # preferred 28 To write: gstate['dummy_var'] = 'DUMMY_VAR_WOOF' 29 gstate[gstate.dummy_var] = gstate.DUMMY_VAR_WOOF # preferred 30 31 """ 32 33 def __init__(self): 34 self._logger = logging.getLogger(__name__) 35 # A map to record the allowed values for each state component. 36 self._allowed_values = {} 37 # The map that stores the current values of all state components. 38 self._values = {} 39 40 # This value can be assigned to any state component to indicate invalid 41 # value. 42 # This is also the default value assigned when the state component is 43 # added. 44 self.INVALID_VALUE = 'INVALID_VALUE' 45 46 47 def __getitem__(self, component): 48 """ 49 Read current value of a state component. 50 51 @param component: The component of interest. 52 53 @return: String value of the state component. 54 55 @raises: StateMachineException if the component does not exist. 56 57 """ 58 if component not in self._values: 59 self._runtime_error('Attempted to read value of unknown component: ' 60 '|%s|' % component) 61 return self._values[component] 62 63 64 def __setitem__(self, component, value): 65 """ 66 Write a new value to the specified state component. 67 68 @param component: The component of interest. 69 70 @param value: String value of the state component 71 72 @raises: StateMachineException if the component does not exist, or if 73 the value provided is not a valid value for the component. 74 75 """ 76 if component not in self._values: 77 self._runtime_error('Attempted to write value to unknown component:' 78 ' |%s|' % component) 79 if value not in self._allowed_values[component]: 80 self._runtime_error('Attempted to write invalid value |%s| to ' 81 'component |%s|. Valid values are %s.' % 82 (value, component, 83 str(self._allowed_values[component]))) 84 self._logger.debug('GlobalState write: [%s: %s --> %s]', 85 component, self._values[component], value) 86 self._values[component] = value 87 88 89 def __delitem__(self, key): 90 self.__runtime_error('Can not delete items from the global state') 91 92 93 def __iter__(self): 94 return iter(self._values) 95 96 97 def __len__(self): 98 return len(self._values) 99 100 101 def __str__(self): 102 return str(self._values) 103 104 105 def __keytransform__(self, key): 106 return key 107 108 109 def _add_state_component(self, component_name, allowed_values): 110 """ 111 Add a state component to the global state. 112 113 @param component_name: The name of the newly created state component. 114 Component names must be unique. Use lower case names. 115 116 @param allowed_values: The list of string values that component_name can 117 take. Use all-caps names / numbers. 118 119 @raises: WardModemSetupException if the component_name exists or if an 120 invalid value is requested to be allowed. 121 122 @raises: TypeError if allowed_values is not a list. 123 124 """ 125 # It is easy to pass in a string by mistake. 126 if type(allowed_values) is not list: 127 raise TypeError('allowed_values must be list of strings.') 128 129 # Add component. 130 if not re.match('[a-z][_a-z0-9]*$', component_name) or \ 131 keyword.iskeyword(component_name): 132 self._setup_error('Component name ill-formed: |%s|' % 133 component_name) 134 if component_name in self._values: 135 self._setup_error('Component already exists: |%s|' % component_name) 136 self._values[component_name] = self.INVALID_VALUE 137 138 # Record allowed values. 139 if self.INVALID_VALUE in allowed_values: 140 self._setup_error('%s can not be an allowed value.' % 141 self.INVALID_VALUE) 142 for allowed_value in allowed_values: 143 if isinstance(allowed_value, str): 144 if not re.match('[A-Z][_A-Z0-9]*$', allowed_value) or \ 145 keyword.iskeyword(component_name): 146 self._setup_error('Allowed value ill-formed: |%s|' % 147 allowed_value) 148 self._allowed_values[component_name] = set(allowed_values) 149 150 151 def _setup_error(self, errstring): 152 """ 153 Log the error and raise WardModemSetupException. 154 155 @param errstring: The error string. 156 157 """ 158 self._logger.error(errstring) 159 raise wme.WardModemSetupException(errstring) 160 161 162 def _runtime_error(self, errstring): 163 """ 164 Log the error and raise StateMachineException. 165 166 @param errstring: The error string. 167 168 """ 169 self._logger.error(errstring) 170 raise wme.StateMachineException(errstring) 171 172 173 class GlobalState(GlobalStateSkeleton): 174 """ 175 All global state is stored in this object. 176 177 This class fills-in state components in the GlobalStateSkeleton. 178 179 @see GlobalStateSkeleton 180 181 """ 182 183 def __init__(self): 184 super(GlobalState, self).__init__() 185 # Used by the state machine request_response. 186 # If enabled, the machine responds to requests, otherwise reports error. 187 # Write: request_response 188 self._add_state_component('request_response_enabled', ['TRUE', 'FALSE']) 189 190 # Used by the state machine power_level_machine. 191 # Store the current power level of the modem. Various operations are 192 # enabled/disabled depending on the power level. 193 # Not all the power level are valid for all modems. 194 # Write: power_level_machine 195 self._add_state_component( 196 'power_level', 197 ['MINIMUM', # Only simple information queries work. 198 'FULL', # All systems up 199 'LOW', # Radio down. Other systems up. 200 'FACTORY_TEST', # Not implemented yet. 201 'OFFLINE', # Not implemented yet. 202 'RESET']) # This state is not actually reached. It causes a 203 # soft reset. 204 205 # The format in which currently selected network operator is displayed. 206 # Write: network_operator_machine 207 self._add_state_component( 208 'operator_format', 209 ['LONG_ALPHANUMERIC', 'SHORT_ALPHANUMERIC', 'NUMERIC']) 210 211 212 # The selected operator. 213 # We allow a modem configuration to supply up to 5 different operators. 214 # Here we try to remember which one is the selected operator currently. 215 # An INVALID_VALUE means that no operator is selected. 216 # Write: network_operator_machine 217 self._add_state_component('operator_index', 218 [0, 1, 2, 3, 4]) 219 220 # The selected network technology. 221 # Write: network_operator_machine 222 self._add_state_component( 223 'access_technology', 224 ['GSM', 'GSM_COMPACT', 'UTRAN', 'GSM_EGPRS', 'UTRAN_HSDPA', 225 'UTRAN_HSUPA', 'UTRAN_HSDPA_HSUPA', 'E_UTRAN']) 226 227 # Select whether a network operator is chosen automatically, and 228 # registration initiated automatically. 229 # Write: network_operator_machine 230 self._add_state_component('automatic_registration', ['TRUE', 'FALSE']) 231 232 # The verbosity level of network registration status unsolicited events. 233 # Write: network_registration_machine 234 self._add_state_component( 235 'unsolicited_registration_status_verbosity', 236 ['SHORT', 'LONG', 'VERY_LONG']) 237 238 # The network registration status. 239 # Write: network_registration_machine 240 self._add_state_component( 241 'registration_status', 242 ['NOT_REGISTERED', 'HOME', 'SEARCHING', 'DENIED', 'UNKNOWN', 243 'ROAMING', 'SMS_ONLY_HOME', 'SMS_ONLY_ROAMING', 'EMERGENCY', 244 'NO_CSFB_HOME', 'NO_CSFB_ROAMING']) 245 246 # The verbosity level of messages sent when network registration status 247 # changes. 248 # Write: network_registration_machine 249 self._add_state_component( 250 'registration_change_message_verbosity', 251 [0, 1, 2,]) 252 253 # These components are level indicators usually used by the phone UI. 254 # Write: level_indicators_machine 255 self._add_state_component('level_battchg', # Battery charge level. 256 [0, 1, 2, 3, 4, 5]) 257 self._add_state_component('level_signal', # Signal quality. 258 [0, 1, 2, 3, 4, 5]) 259 self._add_state_component('level_service', # Service availability. 260 [0, 1]) 261 self._add_state_component('level_sounder', # Sounder activity. 262 [0, 1]) 263 self._add_state_component('level_message', # Message received. 264 [0, 1]) 265 self._add_state_component('level_call', # Call in progress. 266 [0, 1]) 267 self._add_state_component('level_vox', # Transmit activated by voice. 268 [0, 1]) 269 self._add_state_component('level_roam', # Roaming indicator. 270 [0, 1]) 271 self._add_state_component('level_smsfull', # Is the SMS memory full. 272 [0, # Nope, you're fine. 273 1, # Yes, can't receive any more. 274 2]) # Yes, and had to drop some SMSs. 275 self._add_state_component('level_inputstatus', # keypad status. 276 [0, 1]) 277 self._add_state_component('level_gprs_coverage', # Used by Novatel. 278 [0, 1]) 279 self._add_state_component('level_call_setup', # Used by Novatel. 280 [0, 1, 2, 3]) 281 282 # The actual call on a registered network 283 # Write: call_machine 284 self._add_state_component('call_status', ['CONNECTED', 'DISCONNECTED']) 285 286 # Call end reason. Used by E362. 287 # For details, see E362 linux integraion guide. 288 # TODO(pprabhu): Document what the codes mean in E362 specific code. 289 # Write: call_machine 290 self._add_state_component('call_end_reason', [0, 9]) 291