# Copyright (c) 2013 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import collections import keyword import logging import re import wardmodem_exceptions as wme class GlobalStateSkeleton(collections.MutableMapping): """ A skeleton to create global state. The global state should be an object of a derived class. To declare a new state component called dummy_var, with allowed values DUMMY_VAR_WOOF and DUMMY_VAR_POOF, add a call to self._add_state_component('dummy_var', ['DUMMY_VAR_WOOF', 'DUMMY_VAR_POOF']) in __init__ of the derived class. Then any state machine that has the global state object, say gstate, can use the state component, viz, To read: my_state_has_val = gstate['dummy_var'] my_state_has_val = gstate[gstate.dummy_var] # preferred To write: gstate['dummy_var'] = 'DUMMY_VAR_WOOF' gstate[gstate.dummy_var] = gstate.DUMMY_VAR_WOOF # preferred """ def __init__(self): self._logger = logging.getLogger(__name__) # A map to record the allowed values for each state component. self._allowed_values = {} # The map that stores the current values of all state components. self._values = {} # This value can be assigned to any state component to indicate invalid # value. # This is also the default value assigned when the state component is # added. self.INVALID_VALUE = 'INVALID_VALUE' def __getitem__(self, component): """ Read current value of a state component. @param component: The component of interest. @return: String value of the state component. @raises: StateMachineException if the component does not exist. """ if component not in self._values: self._runtime_error('Attempted to read value of unknown component: ' '|%s|' % component) return self._values[component] def __setitem__(self, component, value): """ Write a new value to the specified state component. @param component: The component of interest. @param value: String value of the state component @raises: StateMachineException if the component does not exist, or if the value provided is not a valid value for the component. """ if component not in self._values: self._runtime_error('Attempted to write value to unknown component:' ' |%s|' % component) if value not in self._allowed_values[component]: self._runtime_error('Attempted to write invalid value |%s| to ' 'component |%s|. Valid values are %s.' % (value, component, str(self._allowed_values[component]))) self._logger.debug('GlobalState write: [%s: %s --> %s]', component, self._values[component], value) self._values[component] = value def __delitem__(self, key): self.__runtime_error('Can not delete items from the global state') def __iter__(self): return iter(self._values) def __len__(self): return len(self._values) def __str__(self): return str(self._values) def __keytransform__(self, key): return key def _add_state_component(self, component_name, allowed_values): """ Add a state component to the global state. @param component_name: The name of the newly created state component. Component names must be unique. Use lower case names. @param allowed_values: The list of string values that component_name can take. Use all-caps names / numbers. @raises: WardModemSetupException if the component_name exists or if an invalid value is requested to be allowed. @raises: TypeError if allowed_values is not a list. """ # It is easy to pass in a string by mistake. if type(allowed_values) is not list: raise TypeError('allowed_values must be list of strings.') # Add component. if not re.match('[a-z][_a-z0-9]*$', component_name) or \ keyword.iskeyword(component_name): self._setup_error('Component name ill-formed: |%s|' % component_name) if component_name in self._values: self._setup_error('Component already exists: |%s|' % component_name) self._values[component_name] = self.INVALID_VALUE # Record allowed values. if self.INVALID_VALUE in allowed_values: self._setup_error('%s can not be an allowed value.' % self.INVALID_VALUE) for allowed_value in allowed_values: if isinstance(allowed_value, str): if not re.match('[A-Z][_A-Z0-9]*$', allowed_value) or \ keyword.iskeyword(component_name): self._setup_error('Allowed value ill-formed: |%s|' % allowed_value) self._allowed_values[component_name] = set(allowed_values) def _setup_error(self, errstring): """ Log the error and raise WardModemSetupException. @param errstring: The error string. """ self._logger.error(errstring) raise wme.WardModemSetupException(errstring) def _runtime_error(self, errstring): """ Log the error and raise StateMachineException. @param errstring: The error string. """ self._logger.error(errstring) raise wme.StateMachineException(errstring) class GlobalState(GlobalStateSkeleton): """ All global state is stored in this object. This class fills-in state components in the GlobalStateSkeleton. @see GlobalStateSkeleton """ def __init__(self): super(GlobalState, self).__init__() # Used by the state machine request_response. # If enabled, the machine responds to requests, otherwise reports error. # Write: request_response self._add_state_component('request_response_enabled', ['TRUE', 'FALSE']) # Used by the state machine power_level_machine. # Store the current power level of the modem. Various operations are # enabled/disabled depending on the power level. # Not all the power level are valid for all modems. # Write: power_level_machine self._add_state_component( 'power_level', ['MINIMUM', # Only simple information queries work. 'FULL', # All systems up 'LOW', # Radio down. Other systems up. 'FACTORY_TEST', # Not implemented yet. 'OFFLINE', # Not implemented yet. 'RESET']) # This state is not actually reached. It causes a # soft reset. # The format in which currently selected network operator is displayed. # Write: network_operator_machine self._add_state_component( 'operator_format', ['LONG_ALPHANUMERIC', 'SHORT_ALPHANUMERIC', 'NUMERIC']) # The selected operator. # We allow a modem configuration to supply up to 5 different operators. # Here we try to remember which one is the selected operator currently. # An INVALID_VALUE means that no operator is selected. # Write: network_operator_machine self._add_state_component('operator_index', [0, 1, 2, 3, 4]) # The selected network technology. # Write: network_operator_machine self._add_state_component( 'access_technology', ['GSM', 'GSM_COMPACT', 'UTRAN', 'GSM_EGPRS', 'UTRAN_HSDPA', 'UTRAN_HSUPA', 'UTRAN_HSDPA_HSUPA', 'E_UTRAN']) # Select whether a network operator is chosen automatically, and # registration initiated automatically. # Write: network_operator_machine self._add_state_component('automatic_registration', ['TRUE', 'FALSE']) # The verbosity level of network registration status unsolicited events. # Write: network_registration_machine self._add_state_component( 'unsolicited_registration_status_verbosity', ['SHORT', 'LONG', 'VERY_LONG']) # The network registration status. # Write: network_registration_machine self._add_state_component( 'registration_status', ['NOT_REGISTERED', 'HOME', 'SEARCHING', 'DENIED', 'UNKNOWN', 'ROAMING', 'SMS_ONLY_HOME', 'SMS_ONLY_ROAMING', 'EMERGENCY', 'NO_CSFB_HOME', 'NO_CSFB_ROAMING']) # The verbosity level of messages sent when network registration status # changes. # Write: network_registration_machine self._add_state_component( 'registration_change_message_verbosity', [0, 1, 2,]) # These components are level indicators usually used by the phone UI. # Write: level_indicators_machine self._add_state_component('level_battchg', # Battery charge level. [0, 1, 2, 3, 4, 5]) self._add_state_component('level_signal', # Signal quality. [0, 1, 2, 3, 4, 5]) self._add_state_component('level_service', # Service availability. [0, 1]) self._add_state_component('level_sounder', # Sounder activity. [0, 1]) self._add_state_component('level_message', # Message received. [0, 1]) self._add_state_component('level_call', # Call in progress. [0, 1]) self._add_state_component('level_vox', # Transmit activated by voice. [0, 1]) self._add_state_component('level_roam', # Roaming indicator. [0, 1]) self._add_state_component('level_smsfull', # Is the SMS memory full. [0, # Nope, you're fine. 1, # Yes, can't receive any more. 2]) # Yes, and had to drop some SMSs. self._add_state_component('level_inputstatus', # keypad status. [0, 1]) self._add_state_component('level_gprs_coverage', # Used by Novatel. [0, 1]) self._add_state_component('level_call_setup', # Used by Novatel. [0, 1, 2, 3]) # The actual call on a registered network # Write: call_machine self._add_state_component('call_status', ['CONNECTED', 'DISCONNECTED']) # Call end reason. Used by E362. # For details, see E362 linux integraion guide. # TODO(pprabhu): Document what the codes mean in E362 specific code. # Write: call_machine self._add_state_component('call_end_reason', [0, 9])