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 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