1# Copyright (c) 2012 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 logging
6import subprocess
7
8import pm_errors
9import state_machine
10
11from autotest_lib.client.cros.cellular import mm1_constants
12
13class ConnectMachine(state_machine.StateMachine):
14    """
15    ConnectMachine handles the state transitions involved in bringing the modem
16    to the CONNECTED state.
17
18    """
19    def __init__(self, modem, properties, return_cb, raise_cb):
20        super(ConnectMachine, self).__init__(modem)
21        self.connect_props = properties
22        self.return_cb = return_cb
23        self.raise_cb = raise_cb
24        self.enable_initiated = False
25        self.register_initiated = False
26
27
28    def Cancel(self):
29        """ Overriden from superclass. """
30        logging.info('ConnectMachine: Canceling connect.')
31        super(ConnectMachine, self).Cancel()
32        state = self._modem.Get(mm1_constants.I_MODEM, 'State')
33        reason = mm1_constants.MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED
34        if state == mm1_constants.MM_MODEM_STATE_CONNECTING:
35            logging.info('ConnectMachine: Setting state to REGISTERED.')
36            self._modem.ChangeState(mm1_constants.MM_MODEM_STATE_REGISTERED,
37                                    reason)
38        elif self.enable_initiated and self._modem.enable_step:
39            self._modem.enable_step.Cancel()
40        self._modem.connect_step = None
41
42
43    def _HandleDisabledState(self):
44        logging.info('ConnectMachine: Modem is DISABLED.')
45        assert not self._modem.IsPendingEnable()
46        if self.enable_initiated:
47            message = 'ConnectMachine: Failed to enable modem.'
48            logging.error(message)
49            self.Cancel()
50            self._modem.connect_step = None
51            self.raise_cb(pm_errors.MMCoreError(
52                    pm_errors.MMCoreError.FAILED, message))
53            return False
54        else:
55            logging.info('ConnectMachine: Initiating Enable.')
56            self.enable_initiated = True
57            self._modem.Enable(True)
58
59            # state machine will spin until modem gets enabled,
60            # or if enable fails
61            return True
62
63
64    def _HandleEnablingState(self):
65        logging.info('ConnectMachine: Modem is ENABLING.')
66        assert self._modem.IsPendingEnable()
67        logging.info('ConnectMachine: Waiting for enable.')
68        return True
69
70
71    def _HandleEnabledState(self):
72        logging.info('ConnectMachine: Modem is ENABLED.')
73
74        # Check to see if a register is going on, if not,
75        # start register
76        if self.register_initiated:
77            message = 'ConnectMachine: Failed to register.'
78            logging.error(message)
79            self.Cancel()
80            self._modem.connect_step = None
81            self.raise_cb(pm_errors.MMCoreError(pm_errors.MMCoreError.FAILED,
82                                                message))
83            return False
84        else:
85            logging.info('ConnectMachine: Waiting for Register.')
86            if not self._modem.IsPendingRegister():
87                self._modem.RegisterWithNetwork(
88                        "", self._return_cb, self._raise_cb)
89            self.register_initiated = True
90            return True
91
92
93    def _HandleSearchingState(self):
94        logging.info('ConnectMachine: Modem is SEARCHING.')
95        logging.info('ConnectMachine: Waiting for modem to register.')
96        assert self.register_initiated
97        assert self._modem.IsPendingRegister()
98        return True
99
100
101    def _HandleRegisteredState(self):
102        logging.info('ConnectMachine: Modem is REGISTERED.')
103        assert not self._modem.IsPendingDisconnect()
104        assert not self._modem.IsPendingEnable()
105        assert not self._modem.IsPendingDisable()
106        assert not self._modem.IsPendingRegister()
107        logging.info('ConnectMachine: Setting state to CONNECTING.')
108        reason = mm1_constants.MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED
109        self._modem.ChangeState(mm1_constants.MM_MODEM_STATE_CONNECTING,
110                                reason)
111        return True
112
113
114    def _GetBearerToActivate(self):
115        # Import modem here to avoid circular imports.
116        import modem
117        bearer = None
118        bearer_path = None
119        bearer_props = {}
120        for p, b in self._modem.bearers.iteritems():
121            # assemble bearer props
122            for key, val in self.connect_props.iteritems():
123                if key in modem.ALLOWED_BEARER_PROPERTIES:
124                    bearer_props[key] = val
125            if (b.bearer_properties == bearer_props):
126                logging.info('ConnectMachine: Found matching bearer.')
127                bearer = b
128                bearer_path = p
129                break
130        if bearer is None:
131            assert bearer_path is None
132            logging.info(('ConnectMachine: No matching bearer found, '
133                'creating brearer with properties: ' +
134                str(self.connect_props)))
135            bearer_path = self._modem.CreateBearer(self.connect_props)
136
137        return bearer_path
138
139
140    def _HandleConnectingState(self):
141        logging.info('ConnectMachine: Modem is CONNECTING.')
142        assert not self._modem.IsPendingDisconnect()
143        assert not self._modem.IsPendingEnable()
144        assert not self._modem.IsPendingDisable()
145        assert not self._modem.IsPendingRegister()
146        try:
147            bearer_path = self._GetBearerToActivate()
148            self._modem.ActivateBearer(bearer_path)
149            logging.info('ConnectMachine: Setting state to CONNECTED.')
150            reason = mm1_constants.MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED
151            self._modem.ChangeState(mm1_constants.MM_MODEM_STATE_CONNECTED,
152                                    reason)
153            self._modem.connect_step = None
154            logging.info(
155                'ConnectMachine: Returning bearer path: %s', bearer_path)
156            self.return_cb(bearer_path)
157        except (pm_errors.MMError, subprocess.CalledProcessError) as e:
158            logging.error('ConnectMachine: Failed to connect: ' + str(e))
159            self.raise_cb(e)
160            self._modem.ChangeState(
161                    mm1_constants.MM_MODEM_STATE_REGISTERED,
162                    mm1_constants.MM_MODEM_STATE_CHANGE_REASON_UNKNOWN)
163            self._modem.connect_step = None
164        return False
165
166
167    def _GetModemStateFunctionMap(self):
168        return {
169            mm1_constants.MM_MODEM_STATE_DISABLED:
170                    ConnectMachine._HandleDisabledState,
171            mm1_constants.MM_MODEM_STATE_ENABLING:
172                    ConnectMachine._HandleEnablingState,
173            mm1_constants.MM_MODEM_STATE_ENABLED:
174                    ConnectMachine._HandleEnabledState,
175            mm1_constants.MM_MODEM_STATE_SEARCHING:
176                    ConnectMachine._HandleSearchingState,
177            mm1_constants.MM_MODEM_STATE_REGISTERED:
178                    ConnectMachine._HandleRegisteredState,
179            mm1_constants.MM_MODEM_STATE_CONNECTING:
180                    ConnectMachine._HandleConnectingState
181        }
182
183
184    def _ShouldStartStateMachine(self):
185        if self._modem.connect_step and self._modem.connect_step != self:
186            # There is already a connect operation in progress.
187            message = 'There is already an ongoing connect operation.'
188            logging.error(message)
189            self.raise_cb(pm_errors.MMCoreError(
190                    pm_errors.MMCoreError.IN_PROGRESS, message))
191            return False
192        elif self._modem.connect_step is None:
193            # There is no connect operation going on, cancelled or otherwise.
194            if self._modem.IsPendingDisable():
195                message = 'Modem is currently being disabled. Ignoring ' \
196                          'connect.'
197                logging.error(message)
198                self.raise_cb(
199                    pm_errors.MMCoreError(pm_errors.MMCoreError.WRONG_STATE,
200                                          message))
201                return False
202            state = self._modem.Get(mm1_constants.I_MODEM, 'State')
203            if state == mm1_constants.MM_MODEM_STATE_CONNECTED:
204                message = 'Modem is already connected.'
205                logging.error(message)
206                self.raise_cb(
207                    pm_errors.MMCoreError(pm_errors.MMCoreError.CONNECTED,
208                                          message))
209                return False
210            if state == mm1_constants.MM_MODEM_STATE_DISCONNECTING:
211                assert self._modem.IsPendingDisconnect()
212                message = 'Cannot connect while disconnecting.'
213                logging.error(message)
214                self.raise_cb(
215                    pm_errors.MMCoreError(pm_errors.MMCoreError.WRONG_STATE,
216                                          message))
217                return False
218
219            logging.info('Starting Connect.')
220            self._modem.connect_step = self
221        return True
222