BA / Mbed OS BaBoRo_test2
Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers CellularConnectionFSM.cpp Source File

CellularConnectionFSM.cpp

00001 /*
00002  * Copyright (c) 2017, Arm Limited and affiliates.
00003  * SPDX-License-Identifier: Apache-2.0
00004  *
00005  * Licensed under the Apache License, Version 2.0 (the "License");
00006  * you may not use this file except in compliance with the License.
00007  * You may obtain a copy of the License at
00008  *
00009  *     http://www.apache.org/licenses/LICENSE-2.0
00010  *
00011  * Unless required by applicable law or agreed to in writing, software
00012  * distributed under the License is distributed on an "AS IS" BASIS,
00013  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00014  * See the License for the specific language governing permissions and
00015  * limitations under the License.
00016  */
00017 
00018 #include "CellularConnectionFSM.h"
00019 
00020 #ifdef CELLULAR_DEVICE
00021 
00022 #ifndef MBED_TRACE_MAX_LEVEL
00023 #define MBED_TRACE_MAX_LEVEL TRACE_LEVEL_INFO
00024 #endif
00025 #include "CellularLog.h"
00026 #include "CellularCommon.h"
00027 
00028 // timeout to wait for AT responses
00029 #define TIMEOUT_POWER_ON     (1*1000)
00030 #define TIMEOUT_SIM_PIN      (1*1000)
00031 #define TIMEOUT_NETWORK      (10*1000)
00032 #define TIMEOUT_CONNECT      (60*1000)
00033 #define TIMEOUT_REGISTRATION (180*1000)
00034 
00035 // maximum time when retrying network register, attach and connect in seconds ( 20minutes )
00036 #define TIMEOUT_NETWORK_MAX (20*60)
00037 
00038 #define RETRY_COUNT_DEFAULT 3
00039 
00040 namespace mbed
00041 {
00042 
00043 CellularConnectionFSM::CellularConnectionFSM() :
00044         _serial(0), _state(STATE_INIT), _next_state(_state), _status_callback(0), _event_status_cb(0), _network(0), _power(0), _sim(0),
00045         _queue(8 * EVENTS_EVENT_SIZE), _queue_thread(0), _cellularDevice(0), _retry_count(0), _event_timeout(-1),
00046         _at_queue(8 * EVENTS_EVENT_SIZE), _event_id(0)
00047 {
00048     memset(_sim_pin, 0, sizeof(_sim_pin));
00049 #if MBED_CONF_CELLULAR_RANDOM_MAX_START_DELAY == 0
00050     _start_time = 0;
00051 #else
00052     // so that not every device don't start at the exact same time (for example after power outage)
00053     _start_time = rand() % (MBED_CONF_CELLULAR_RANDOM_MAX_START_DELAY);
00054 #endif // MBED_CONF_CELLULAR_RANDOM_MAX_START_DELAY
00055 
00056     // set initial retry values in seconds
00057     _retry_timeout_array[0] = 1; // double time on each retry in order to keep network happy
00058     _retry_timeout_array[1] = 2;
00059     _retry_timeout_array[2] = 4;
00060     _retry_timeout_array[3] = 8;
00061     _retry_timeout_array[4] = 16;
00062     _retry_timeout_array[5] = 32;
00063     _retry_timeout_array[6] = 64;
00064     _retry_timeout_array[7] = 128; // if around two minutes was not enough then let's wait much longer
00065     _retry_timeout_array[8] = 600;
00066     _retry_timeout_array[9] = TIMEOUT_NETWORK_MAX;
00067     _retry_array_length = MAX_RETRY_ARRAY_SIZE;
00068 }
00069 
00070 CellularConnectionFSM::~CellularConnectionFSM()
00071 {
00072     stop();
00073     delete _cellularDevice;
00074 }
00075 
00076 void CellularConnectionFSM::stop()
00077 {
00078     tr_info("CellularConnectionUtil::stop");
00079     if (_cellularDevice) {
00080         _cellularDevice->close_power();
00081         _cellularDevice->close_network();
00082         _cellularDevice->close_sim();
00083         _power = NULL;
00084         _network = NULL;
00085         _sim = NULL;
00086     }
00087     if (_queue_thread) {
00088         _queue_thread->terminate();
00089         delete _queue_thread;
00090         _queue_thread = NULL;
00091     }
00092 }
00093 
00094 nsapi_error_t CellularConnectionFSM::init()
00095 {
00096     tr_info("CELLULAR_DEVICE: %s", CELLULAR_STRINGIFY(CELLULAR_DEVICE));
00097     _cellularDevice = new CELLULAR_DEVICE(_at_queue);
00098     if (!_cellularDevice) {
00099         stop();
00100         return NSAPI_ERROR_NO_MEMORY ;
00101     }
00102 
00103     _power = _cellularDevice->open_power(_serial);
00104     if (!_power) {
00105         stop();
00106         return NSAPI_ERROR_NO_MEMORY ;
00107     }
00108     _network = _cellularDevice->open_network(_serial);
00109     if (!_network) {
00110         stop();
00111         return NSAPI_ERROR_NO_MEMORY ;
00112     }
00113 
00114     _sim = _cellularDevice->open_sim(_serial);
00115     if (!_sim) {
00116         stop();
00117         return NSAPI_ERROR_NO_MEMORY ;
00118     }
00119 
00120     _at_queue.chain(&_queue);
00121 
00122     _retry_count = 0;
00123     _state = STATE_INIT;
00124     _next_state = STATE_INIT;
00125 
00126     return _network->init();
00127 }
00128 
00129 bool CellularConnectionFSM::power_on()
00130 {
00131     nsapi_error_t err = _power->on();
00132     if (err != NSAPI_ERROR_OK  && err != NSAPI_ERROR_UNSUPPORTED ) {
00133         tr_warn("Cellular start failed. Power off/on.");
00134         err = _power->off();
00135         if (err != NSAPI_ERROR_OK  && err != NSAPI_ERROR_UNSUPPORTED ) {
00136             tr_error("Cellular power down failed!");
00137         }
00138         return false;
00139     }
00140     return true;
00141 }
00142 
00143 void CellularConnectionFSM::set_sim_pin(const char * sim_pin)
00144 {
00145     strncpy(_sim_pin, sim_pin, sizeof(_sim_pin));
00146     _sim_pin[sizeof(_sim_pin)-1] = '\0';
00147 }
00148 
00149 bool CellularConnectionFSM::open_sim()
00150 {
00151     CellularSIM::SimState state = CellularSIM::SimStateUnknown;
00152     // wait until SIM is readable
00153     // here you could add wait(secs) if you know start delay of your SIM
00154     if (_sim->get_sim_state(state) != NSAPI_ERROR_OK ) {
00155         tr_info("Waiting for SIM (err while reading)...");
00156         return false;
00157     }
00158 
00159     switch (state) {
00160         case CellularSIM::SimStateReady:
00161             tr_info("SIM Ready");
00162             break;
00163         case CellularSIM::SimStatePinNeeded: {
00164             if (strlen(_sim_pin)) {
00165                 tr_info("SIM pin required, entering pin: %s", _sim_pin);
00166                 nsapi_error_t err = _sim->set_pin(_sim_pin);
00167                 if (err) {
00168                     tr_error("SIM pin set failed with: %d, bailing out...", err);
00169                 }
00170             } else {
00171                 tr_warn("PIN required but No SIM pin provided.");
00172             }
00173         }
00174             break;
00175         case CellularSIM::SimStatePukNeeded:
00176             tr_info("SIM PUK code needed...");
00177             break;
00178         case CellularSIM::SimStateUnknown:
00179             tr_info("SIM, unknown state...");
00180             break;
00181         default:
00182             MBED_ASSERT(1);
00183             break;
00184     }
00185 
00186     if (_event_status_cb) {
00187         _event_status_cb((nsapi_event_t)CellularSIMStatusChanged, state);
00188     }
00189 
00190     return state == CellularSIM::SimStateReady;
00191 }
00192 
00193 bool CellularConnectionFSM::set_network_registration(char *plmn)
00194 {
00195     if (_network->set_registration(plmn) != NSAPI_ERROR_OK ) {
00196         tr_error("Failed to set network registration.");
00197         return false;
00198     }
00199     return true;
00200 }
00201 
00202 bool CellularConnectionFSM::is_registered()
00203 {
00204     CellularNetwork::RegistrationStatus status;
00205     bool is_registered = false;
00206 
00207     for (int type = 0; type < CellularNetwork::C_MAX; type++) {
00208         if (get_network_registration((CellularNetwork::RegistrationType) type, status, is_registered)) {
00209             tr_debug("get_network_registration: type=%d, status=%d", type, status);
00210             if (is_registered) {
00211                 break;
00212             }
00213         }
00214     }
00215 
00216     return is_registered;
00217 }
00218 
00219 bool CellularConnectionFSM::get_network_registration(CellularNetwork::RegistrationType type,
00220         CellularNetwork::RegistrationStatus &status, bool &is_registered)
00221 {
00222     is_registered = false;
00223     bool is_roaming = false;
00224     nsapi_error_t err = _network->get_registration_status(type, status);
00225     if (err != NSAPI_ERROR_OK ) {
00226         if (err != NSAPI_ERROR_UNSUPPORTED ) {
00227             tr_warn("Get network registration failed (type %d)!", type);
00228         }
00229         return false;
00230     }
00231     switch (status) {
00232         case CellularNetwork::RegisteredRoaming:
00233             is_roaming = true;
00234         // fall-through
00235         case CellularNetwork::RegisteredHomeNetwork:
00236             is_registered = true;
00237             break;
00238         case CellularNetwork::RegisteredSMSOnlyRoaming:
00239             is_roaming = true;
00240         // fall-through
00241         case CellularNetwork::RegisteredSMSOnlyHome:
00242             tr_warn("SMS only network registration!");
00243             break;
00244         case CellularNetwork::RegisteredCSFBNotPreferredRoaming:
00245             is_roaming = true;
00246         // fall-through
00247         case CellularNetwork::RegisteredCSFBNotPreferredHome:
00248             tr_warn("Not preferred network registration!");
00249             break;
00250         case CellularNetwork::AttachedEmergencyOnly:
00251             tr_warn("Emergency only network registration!");
00252             break;
00253         case CellularNetwork::RegistrationDenied:
00254         case CellularNetwork::NotRegistered:
00255         case CellularNetwork::Unknown:
00256         case CellularNetwork::SearchingNetwork:
00257         default:
00258             break;
00259     }
00260 
00261     if (is_roaming) {
00262         tr_warn("Roaming cellular network!");
00263     }
00264 
00265     return true;
00266 }
00267 
00268 bool CellularConnectionFSM::get_attach_network(CellularNetwork::AttachStatus &status)
00269 {
00270     nsapi_error_t err = _network->get_attach(status);
00271     if (err != NSAPI_ERROR_OK ) {
00272         return false;
00273     }
00274     return true;
00275 }
00276 
00277 bool CellularConnectionFSM::set_attach_network()
00278 {
00279     nsapi_error_t attach_err = _network->set_attach();
00280     if (attach_err != NSAPI_ERROR_OK ) {
00281         return false;
00282     }
00283     return true;
00284 }
00285 
00286 void CellularConnectionFSM::report_failure(const char* msg)
00287 {
00288     tr_error("Cellular network failed: %s", msg);
00289     if (_status_callback) {
00290         _status_callback(_state, _next_state);
00291     }
00292 }
00293 
00294 const char* CellularConnectionFSM::get_state_string(CellularState state)
00295 {
00296     static const char *strings[] = { "Init", "Power", "Device ready", "SIM pin", "Registering network", "Attaching network", "Activating PDP Context", "Connecting network", "Connected"};
00297     return strings[state];
00298 }
00299 
00300 nsapi_error_t CellularConnectionFSM::is_automatic_registering(bool& auto_reg)
00301 {
00302     CellularNetwork::NWRegisteringMode mode;
00303     nsapi_error_t err = _network->get_network_registering_mode(mode);
00304     if (err == NSAPI_ERROR_OK ) {
00305         tr_debug("automatic registering mode: %d", mode);
00306         auto_reg = (mode == CellularNetwork::NWModeAutomatic);
00307     }
00308     return err;
00309 }
00310 
00311 nsapi_error_t CellularConnectionFSM::continue_from_state(CellularState state)
00312 {
00313     tr_info("Continue state from %s to %s", get_state_string((CellularConnectionFSM::CellularState)_state),
00314             get_state_string((CellularConnectionFSM::CellularState)state));
00315     _state = state;
00316     _next_state = state;
00317     _retry_count = 0;
00318     if (!_queue.call_in(0, callback(this, &CellularConnectionFSM::event))) {
00319         stop();
00320         return NSAPI_ERROR_NO_MEMORY ;
00321     }
00322 
00323     return NSAPI_ERROR_OK ;
00324 }
00325 
00326 nsapi_error_t CellularConnectionFSM::continue_to_state(CellularState state)
00327 {
00328     _retry_count = 0;
00329     if (state < _state) {
00330         _state = state;
00331     } else {
00332         // update next state so that we don't continue from previous state
00333         _state = _next_state;
00334     }
00335     if (!_queue.call_in(0, callback(this, &CellularConnectionFSM::event))) {
00336         stop();
00337         return NSAPI_ERROR_NO_MEMORY ;
00338     }
00339 
00340     return NSAPI_ERROR_OK ;
00341 }
00342 
00343 void CellularConnectionFSM::enter_to_state(CellularState state)
00344 {
00345     _next_state = state;
00346     _retry_count = 0;
00347 }
00348 
00349 void CellularConnectionFSM::retry_state_or_fail()
00350 {
00351     if (++_retry_count < MAX_RETRY_ARRAY_SIZE) {
00352         tr_debug("Retry State %s, retry %d/%d", get_state_string(_state), _retry_count, MAX_RETRY_ARRAY_SIZE);
00353         _event_timeout = _retry_timeout_array[_retry_count];
00354     } else {
00355         report_failure(get_state_string(_state));
00356         return;
00357     }
00358 }
00359 
00360 void CellularConnectionFSM::state_init()
00361 {
00362     _event_timeout = _start_time;
00363     tr_info("Init state, waiting %d ms before POWER state)", _start_time);
00364     enter_to_state(STATE_POWER_ON);
00365 }
00366 
00367 void CellularConnectionFSM::state_power_on()
00368 {
00369     _cellularDevice->set_timeout(TIMEOUT_POWER_ON);
00370     tr_info("Cellular power ON (timeout %d ms)", TIMEOUT_POWER_ON);
00371     if (power_on()) {
00372         enter_to_state(STATE_DEVICE_READY);
00373     } else {
00374         // retry to power on device
00375         retry_state_or_fail();
00376     }
00377 }
00378 
00379 bool CellularConnectionFSM::device_ready()
00380 {
00381     tr_info("Cellular device ready");
00382     if (_event_status_cb) {
00383         _event_status_cb((nsapi_event_t)CellularDeviceReady, 0);
00384     }
00385 
00386     _power->remove_device_ready_urc_cb(mbed::callback(this, &CellularConnectionFSM::ready_urc_cb));
00387 
00388     bool success = false;
00389     for (int type = 0; type < CellularNetwork::C_MAX; type++) {
00390         if (!_network->set_registration_urc((CellularNetwork::RegistrationType)type, true)) {
00391             success = true;
00392         }
00393     }
00394     if (!success) {
00395         tr_error("Failed to set any URC's for registration");
00396         report_failure(get_state_string(_state));
00397         return false;
00398     }
00399 
00400     return true;
00401 }
00402 
00403 void CellularConnectionFSM::state_device_ready()
00404 {
00405     _cellularDevice->set_timeout(TIMEOUT_POWER_ON);
00406     if (_power->set_at_mode() == NSAPI_ERROR_OK ) {
00407         if (device_ready()) {
00408             enter_to_state(STATE_SIM_PIN);
00409         }
00410     } else {
00411         if (_retry_count == 0) {
00412             (void)_power->set_device_ready_urc_cb(mbed::callback(this, &CellularConnectionFSM::ready_urc_cb));
00413         }
00414         retry_state_or_fail();
00415     }
00416 }
00417 
00418 void CellularConnectionFSM::state_sim_pin()
00419 {
00420     _cellularDevice->set_timeout(TIMEOUT_SIM_PIN);
00421     tr_info("Sim state (timeout %d ms)", TIMEOUT_SIM_PIN);
00422     if (open_sim()) {
00423         enter_to_state(STATE_REGISTERING_NETWORK);
00424     } else {
00425         retry_state_or_fail();
00426     }
00427 }
00428 
00429 void CellularConnectionFSM::state_registering()
00430 {
00431     _cellularDevice->set_timeout(TIMEOUT_NETWORK);
00432     if (is_registered()) {
00433         // we are already registered, go to attach
00434         enter_to_state(STATE_ATTACHING_NETWORK);
00435     } else {
00436         bool auto_reg = false;
00437         nsapi_error_t err = is_automatic_registering(auto_reg);
00438         if (err == NSAPI_ERROR_OK  && !auto_reg) { // when we support plmn add this :  || plmn
00439             // automatic registering is not on, set registration and retry
00440             _cellularDevice->set_timeout(TIMEOUT_REGISTRATION);
00441             set_network_registration();
00442         }
00443         retry_state_or_fail();
00444     }
00445 }
00446 
00447 void CellularConnectionFSM::state_attaching()
00448 {
00449     _cellularDevice->set_timeout(TIMEOUT_CONNECT);
00450     CellularNetwork::AttachStatus attach_status;
00451     if (get_attach_network(attach_status)) {
00452         if (attach_status == CellularNetwork::Attached) {
00453             enter_to_state(STATE_ACTIVATING_PDP_CONTEXT);
00454         } else {
00455             set_attach_network();
00456             retry_state_or_fail();
00457         }
00458     } else {
00459         retry_state_or_fail();
00460     }
00461 }
00462 
00463 void CellularConnectionFSM::state_activating_pdp_context()
00464 {
00465     _cellularDevice->set_timeout(TIMEOUT_CONNECT);
00466     tr_info("Activate PDP Context (timeout %d ms)", TIMEOUT_CONNECT);
00467     if (_network->activate_context() == NSAPI_ERROR_OK ) {
00468         // when using modems stack connect is synchronous
00469         _next_state = STATE_CONNECTING_NETWORK;
00470     } else {
00471         retry_state_or_fail();
00472     }
00473 }
00474 
00475 void CellularConnectionFSM::state_connect_to_network()
00476 {
00477     _cellularDevice->set_timeout(TIMEOUT_CONNECT);
00478     tr_info("Connect to cellular network (timeout %d ms)", TIMEOUT_CONNECT);
00479     if (_network->connect() == NSAPI_ERROR_OK ) {
00480         _cellularDevice->set_timeout(TIMEOUT_NETWORK);
00481         tr_debug("Connected to cellular network, set at timeout (timeout %d ms)", TIMEOUT_NETWORK);
00482         // when using modems stack connect is synchronous
00483         _next_state = STATE_CONNECTED;
00484     } else {
00485         retry_state_or_fail();
00486     }
00487 }
00488 
00489 void CellularConnectionFSM::state_connected()
00490 {
00491     _cellularDevice->set_timeout(TIMEOUT_NETWORK);
00492     tr_debug("Cellular ready! (timeout %d ms)", TIMEOUT_NETWORK);
00493     if (_status_callback) {
00494         _status_callback(_state, _next_state);
00495     }
00496 }
00497 
00498 void CellularConnectionFSM::event()
00499 {
00500     _event_timeout = -1;
00501     switch (_state) {
00502         case STATE_INIT:
00503             state_init();
00504             break;
00505         case STATE_POWER_ON:
00506             state_power_on();
00507             break;
00508         case STATE_DEVICE_READY:
00509             state_device_ready();
00510             break;
00511         case STATE_SIM_PIN:
00512             state_sim_pin();
00513             break;
00514         case STATE_REGISTERING_NETWORK:
00515             state_registering();
00516             break;
00517         case STATE_ATTACHING_NETWORK:
00518             state_attaching();
00519             break;
00520         case STATE_ACTIVATING_PDP_CONTEXT:
00521             state_activating_pdp_context();
00522             break;
00523         case STATE_CONNECTING_NETWORK:
00524             state_connect_to_network();
00525             break;
00526         case STATE_CONNECTED:
00527             state_connected();
00528             break;
00529         default:
00530             MBED_ASSERT(0);
00531             break;
00532     }
00533 
00534     if (_next_state != _state || _event_timeout >= 0) {
00535         if (_next_state != _state) { // state exit condition
00536             tr_info("Cellular state from %s to %s", get_state_string((CellularConnectionFSM::CellularState)_state),
00537                     get_state_string((CellularConnectionFSM::CellularState)_next_state));
00538             if (_status_callback) {
00539                 if (!_status_callback(_state, _next_state)) {
00540                     return;
00541                 }
00542             }
00543         } else {
00544             tr_info("Cellular event in %d seconds", _event_timeout);
00545         }
00546         _state = _next_state;
00547         if (_event_timeout == -1) {
00548             _event_timeout = 0;
00549         }
00550         _event_id = _queue.call_in(_event_timeout*1000, callback(this, &CellularConnectionFSM::event));
00551         if (!_event_id) {
00552             report_failure("Cellular event failure!");
00553             return;
00554         }
00555     }
00556 }
00557 
00558 nsapi_error_t CellularConnectionFSM::start_dispatch()
00559 {
00560     MBED_ASSERT(!_queue_thread);
00561 
00562     _queue_thread = new rtos::Thread(osPriorityNormal, 2048);
00563     if (!_queue_thread) {
00564         stop();
00565         return NSAPI_ERROR_NO_MEMORY ;
00566     }
00567     if (_queue_thread->start(callback(&_queue, &events::EventQueue::dispatch_forever)) != osOK) {
00568         stop();
00569         return NSAPI_ERROR_NO_MEMORY ;
00570     }
00571 
00572     return NSAPI_ERROR_OK ;
00573 }
00574 
00575 void CellularConnectionFSM::set_serial(UARTSerial *serial)
00576 {
00577     _serial = serial;
00578 }
00579 
00580 void CellularConnectionFSM::set_callback(mbed::Callback<bool(int, int)> status_callback)
00581 {
00582     _status_callback = status_callback;
00583 }
00584 
00585 void CellularConnectionFSM::attach(mbed::Callback<void(nsapi_event_t, intptr_t)> status_cb)
00586 {
00587     _event_status_cb = status_cb;
00588     _network->attach(callback(this, &CellularConnectionFSM::network_callback));
00589 }
00590 
00591 void CellularConnectionFSM::network_callback(nsapi_event_t ev, intptr_t ptr)
00592 {
00593 
00594     tr_info("FSM: network_callback called with event: %d, intptr: %d", ev, ptr);
00595     if ((cellular_connection_status_t)ev == CellularRegistrationStatusChanged && _state == STATE_REGISTERING_NETWORK) {
00596         // expect packet data so only these states are valid
00597         if (ptr == CellularNetwork::RegisteredHomeNetwork && CellularNetwork::RegisteredRoaming) {
00598             _queue.cancel(_event_id);
00599             continue_from_state(STATE_ATTACHING_NETWORK);
00600         }
00601     }
00602 
00603     if (_event_status_cb) {
00604         _event_status_cb(ev, ptr);
00605     }
00606 }
00607 
00608 void CellularConnectionFSM::ready_urc_cb()
00609 {
00610     tr_debug("Device ready URC func called");
00611     if (_state == STATE_DEVICE_READY && _power->set_at_mode() == NSAPI_ERROR_OK ) {
00612         tr_debug("State was STATE_DEVICE_READY and at mode ready, cancel state and move to next");
00613         _queue.cancel(_event_id);
00614         if (device_ready()) {
00615             continue_from_state(STATE_SIM_PIN);
00616         }
00617     }
00618 }
00619 
00620 events::EventQueue *CellularConnectionFSM::get_queue()
00621 {
00622     return &_queue;
00623 }
00624 
00625 CellularNetwork* CellularConnectionFSM::get_network()
00626 {
00627     return _network;
00628 }
00629 
00630 CellularDevice* CellularConnectionFSM::get_device()
00631 {
00632     return _cellularDevice;
00633 }
00634 
00635 CellularSIM* CellularConnectionFSM::get_sim()
00636 {
00637     return _sim;
00638 }
00639 
00640 NetworkStack *CellularConnectionFSM::get_stack()
00641 {
00642     return _cellularDevice->get_stack();
00643 }
00644 
00645 void CellularConnectionFSM::set_retry_timeout_array(uint16_t timeout[], int array_len)
00646 {
00647     _retry_array_length = array_len > MAX_RETRY_ARRAY_SIZE ? MAX_RETRY_ARRAY_SIZE : array_len;
00648 
00649     for (int i = 0; i < _retry_array_length; i++) {
00650         _retry_timeout_array[i] = timeout[i];
00651     }
00652 }
00653 
00654 } // namespace
00655 
00656 #endif // CELLULAR_DEVICE