Vodafone K3770/K3772-Z modems driver & networking library

Dependencies:   Socket USBHostWANDongle lwip-sys lwip

Dependents:   VodafoneUSBModemHTTPClientTest VodafoneUSBModemNTPClientTest VodafoneUSBModemSMSTest VodafoneUSBModemUSSDTest ... more

Fork of VodafoneUSBModem_bleedingedge by Donatien Garnier

This is the driver for the Vodafone K3700 & K3772-Z Dongles:

K3770

More details and instructions can be found here.

VodafoneK3770.cpp

Committer:
donatien
Date:
2012-07-06
Revision:
10:21a6f09d5631
Parent:
8:04b6a042595f
Child:
11:565b2ec40dea

File content as of revision 10:21a6f09d5631:

/* VodafoneK3770.cpp */
/*
Copyright (C) 2012 ARM Limited.

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#define __DEBUG__ 4
#ifndef __MODULE__
#define __MODULE__ "VodafoneK3770.cpp"
#endif

#include "core/fwk.h"

#include "VodafoneK3770.h"

VodafoneK3770::VodafoneK3770() : m_dongle(),
m_atStream(m_dongle.getSerial(1)), m_pppStream(m_dongle.getSerial(0)), m_at(&m_atStream),
m_sms(&m_at), m_ussd(&m_at), m_ppp(&m_pppStream), 
m_dongleConnected(false), m_ipInit(false), m_smsInit(false), m_atOpen(false)
{

}

class CREGProcessor : public IATCommandsProcessor
{
public:
  CREGProcessor() : status(STATUS_REGISTERING)
  {

  }
  enum REGISTERING_STATUS { STATUS_REGISTERING, STATUS_OK, STATUS_FAILED };
  REGISTERING_STATUS getStatus()
  {
    return status;
  }
private:
  virtual int onNewATResponseLine(ATCommandsInterface* pInst, const char* line)
  {
    int r;
    if( sscanf(line, "+CREG: %*d,%d", &r) == 1 )
    {
      switch(r)
      {
      case 1:
      case 5:
        status = STATUS_OK;
        break;
      case 0:
      case 2:
        status = STATUS_REGISTERING;
        break;
      case 3:
      default:
        status = STATUS_FAILED;
        break;
      }
    }
    return OK;
  }
  virtual int onNewEntryPrompt(ATCommandsInterface* pInst)
  {
    return OK;
  }
  volatile REGISTERING_STATUS status;
};

class COPSProcessor : public IATCommandsProcessor
{
public:
  COPSProcessor() : valid(false)
  {
    network[0] = '\0';
    apn[0] = '\0';
    bearer[0] = '\0';
  }
  char* getNetwork()
  {
    return network;
  }
  char* getAPN()
  {
    return apn;
  }
  char* getBearer()
  {
    return bearer;
  }
  bool isValid()
  {
    return valid;
  }
private:
  virtual int onNewATResponseLine(ATCommandsInterface* pInst, const char* line)
  {
    int networkId;
    int bearerId;
    int s = sscanf(line, "+COPS: %*d,%*d,\"%d\",%d", &networkId, &bearerId);
    if( s == 2 )
    {
      switch(networkId)
      {
      case 23415:
        strcpy(network, "Vodafone UK");
        strcpy(apn, "pp.vodafone.co.uk");
        valid = true;
        break;
      case 20810:
        strcpy(network, "SFR FR");
        strcpy(apn, "websfr");
        valid = true;
        break;
      default:
        break;
      }
    }
    else
    {
      return OK;
    }
    switch(bearerId)
    {
    case 0: strcpy(bearer, "GSM"); break;
    case 1: strcpy(bearer, "GSM Compact"); break;
    case 2: strcpy(bearer, "UTRAN"); break;
    case 3: strcpy(bearer, "GSM w/EGPRS"); break;
    case 4: strcpy(bearer, "UTRAN w/HSDPA"); break;
    case 5: strcpy(bearer, "UTRAN w/HSUPA"); break;
    case 6: strcpy(bearer, "UTRAN w/HSDPA and HSUPA"); break;
    case 7: strcpy(bearer, "E-UTRAN"); break;

    default:
      break;
    }
    return OK;
  }
  virtual int onNewEntryPrompt(ATCommandsInterface* pInst)
  {
    return OK;
  }
  char network[24];
  char bearer[24];
  char apn[24];
  volatile bool valid;
};


int VodafoneK3770::connect(const char* apn, const char* user, const char* password)
{
  if( !m_ipInit )
  {
    m_ipInit = true;
    m_ppp.init();
  }
  m_ppp.setup(user, password);

  int ret = init();
  if(ret)
  {
    return ret;
  }

  m_smsInit = false; //SMS status reset
  m_ussdInit = false; //USSD status reset

  ATCommandsInterface::ATResult result;

#if 0
  //Get network info & select corresponding APN
  COPSProcessor copsProcessor;
  DBG("Get network info & select APN from DB");
  ret = m_at.execute("AT+COPS=,2;+COPS?", &copsProcessor, &result); //Configure to get operator's info in numeric code & get operator's id
  DBG("Result of command: Err code=%d", ret);
  DBG("ATResult: AT return=%d (code %d)", result.result, result.code);

  if(!copsProcessor.isValid())
  {
    WARN("Connected to an unknown network, try to connect with default parameters");
    DBG("Connected with %s", copsProcessor.getBearer());
  }
  else
  {
    DBG("Connected to %s with %s", copsProcessor.getNetwork(), copsProcessor.getBearer());
    char cmd[48];
    sprintf(cmd, "AT+CGDCONT=1,\"IP\",\"%s\"", copsProcessor.getAPN());
    ret = m_at.executeSimple(cmd, &result);
    DBG("Result of command: Err code=%d", ret);
    DBG("ATResult: AT return=%d (code %d)", result.result, result.code);
    DBG("APN set to %s", copsProcessor.getAPN());
  }
  #else
  if(apn != NULL)
  {
    char cmd[48];
    sprintf(cmd, "AT+CGDCONT=1,\"IP\",\"%s\"", apn);
    ret = m_at.executeSimple(cmd, &result);
    DBG("Result of command: Err code=%d", ret);
    DBG("ATResult: AT return=%d (code %d)", result.result, result.code);
    DBG("APN set to %s", apn);
  }
  #endif


  //Connect
  DBG("Connecting");
  ret = m_at.executeSimple("ATDT *99#", &result);
  DBG("Result of command: Err code=%d", ret);
  DBG("ATResult: AT return=%d (code %d)", result.result, result.code);
  m_at.close(); // Closing AT parser
  m_atOpen = false; //Will need to be reinitialized afterwards

  DBG("AT Parser closed");
  if( (ret!=NET_MOREINFO) || (result.result != ATCommandsInterface::ATResult::AT_CONNECT))
  {
    ERR("Could not connect");
    return ret; //Could not connect
  }
  DBG("Connecting PPP");

  ret = m_ppp.connect();
  DBG("Result of connect: Err code=%d", ret);
  return ret;
}


int VodafoneK3770::disconnect()
{
  DBG("Disconnecting from PPP");
  int ret = m_ppp.disconnect();
  if(ret)
  {
    ERR("Disconnect returned %d, still trying to disconnect", ret);
  }

  //Ugly but leave dongle time to recover
  Thread::wait(500);

  ATCommandsInterface::ATResult result;
  DBG("Starting AT thread");
  ret = m_at.open();
  if(ret)
  {
    return ret;
  }

  DBG("Trying to hangup");

#if 0 //Does not appear to work
  int tries = 10;
  do
  {
    ret = m_at.executeSimple("+++", &result, 1000);
    DBG("Result of command: Err code=%d\n", ret);
    DBG("ATResult: AT return=%d (code %d)\n", result.result, result.code);
  } while(tries-- && ret);
  if(!ret)
  {
    ret = m_at.executeSimple("ATH", &result);
    DBG("Result of command: Err code=%d\n", ret);
    DBG("ATResult: AT return=%d (code %d)\n", result.result, result.code);
  }
#endif

  //Reinit AT parser
  ret = m_at.init();
  DBG("Result of command: Err code=%d\n", ret);
  if(ret)
  {
    m_at.close(); // Closing AT parser
    DBG("AT Parser closed, could not complete disconnection");
    return NET_TIMEOUT;
  }

  m_at.close(); // Closing AT parser
  DBG("AT Parser closed");
  return OK;
}

int VodafoneK3770::sendSM(const char* number, const char* message)
{
  int ret = init();
  if(ret)
  {
    return ret;
  }

  if(!m_smsInit)
  {
    ret = m_sms.init();
    if(ret)
    {
      return ret;
    }
    m_smsInit = true;
  }

  ret = m_sms.send(number, message);
  if(ret)
  {
    return ret;
  }

  return OK;
}

int VodafoneK3770::getSM(char* number, char* message, size_t maxLength)
{
  int ret = init();
  if(ret)
  {
    return ret;
  }

  if(!m_smsInit)
  {
    ret = m_sms.init();
    if(ret)
    {
      return ret;
    }
    m_smsInit = true;
  }

  ret = m_sms.get(number, message, maxLength);
  if(ret)
  {
    return ret;
  }

  return OK;
}

int VodafoneK3770::getSMCount(size_t* pCount)
{
  int ret = init();
  if(ret)
  {
    return ret;
  }

  if(!m_smsInit)
  {
    ret = m_sms.init();
    if(ret)
    {
      return ret;
    }
    m_smsInit = true;
  }

  ret = m_sms.getCount(pCount);
  if(ret)
  {
    return ret;
  }

  return OK;
}

int VodafoneK3770::sendUSSD(const char* command, char* result, size_t maxLength)
{
  int ret = init();
  if(ret)
  {
    return ret;
  }

  if(!m_ussdInit)
  {
    ret = m_ussd.init();
    if(ret)
    {
      return ret;
    }
    m_ussdInit = true;
  }

  ret = m_ussd.send(command, result, maxLength);
  if(ret)
  {
    return ret;
  }

  return OK;
}

ATCommandsInterface* VodafoneK3770::getATCommandsInterface()
{
  return &m_at;
}

int VodafoneK3770::init()
{
  if( !m_dongleConnected )
  {
    m_dongleConnected = true;
    while( !m_dongle.connected() )
    {
      m_dongle.tryConnect();
      Thread::wait(10);
    }
  }

  if(m_atOpen)
  {
    return OK;
  }

  DBG("Starting AT thread if needed");
  int ret = m_at.open();
  if(ret)
  {
    return ret;
  }

  DBG("Sending initialisation commands");
  ret = m_at.init();
  if(ret)
  {
    return ret;
  }

  DBG("Configuring unsolicited result codes support properly");
  //Configuring port
  ret = m_at.executeSimple("AT^CURC=0;^PORTSEL=1", NULL); //Huawei-specific, not 3GPP-compliant
  if(ret != OK)
  {
    return NET_PROTOCOL;
  }

  ATCommandsInterface::ATResult result;

  //Wait for network registration
  CREGProcessor cregProcessor;
  do
  {
    DBG("Waiting for network registration");
    ret = m_at.execute("AT+CREG?", &cregProcessor, &result);
    DBG("Result of command: Err code=%d\n", ret);
    DBG("ATResult: AT return=%d (code %d)\n", result.result, result.code);
    if(cregProcessor.getStatus() == CREGProcessor::STATUS_REGISTERING)
    {
      Thread::wait(3000);
    }
  } while(cregProcessor.getStatus() == CREGProcessor::STATUS_REGISTERING);
  if(cregProcessor.getStatus() == CREGProcessor::STATUS_FAILED)
  {
    ERR("Registration denied");
    return NET_AUTH;
  }

  m_atOpen = true;

  return OK;
}