普通文本  |  291行  |  11.16 KB

# 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])