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
5import collections
6import keyword
7import logging
8import re
9
10import wardmodem_exceptions as wme
11
12class 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
173class 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