Большинство GSM модулей работают по интерфейсу UART, посредством AT-команд. Но для серьезных проектов, использование AT команд несет определенные трудности:
• контроль и обработка ошибок
• результат выполнения команды возвращается с длительной задержкой
• необходимо разбирать входящие строки налету
Нужно понимать, что с результатом выполнения команды, в буфер может попасть URC-код от входящего звонка, SMS, принятые данные и пр. Входной буфер с принятыми строками, приходится разбирать опираясь лишь на символы переносов и «эхо» команды, а сами команды зачастую сильно отличаются форматом. По этим причинам, использование AT вносит дополнительную задержку, алгоритмически ее устранить практически невозможно, ведь причина находится в самом модуле и несовершенстве его встроенного ПО
В этом примере я использовал SIM800C. Посмотрев спецификацию и убедившись в поддержке PPP, стал изучать способы реализации. Для использования PPP, модуль переключается несколькими настроечными командами, после этого режим AT становится недоступным и фактически идет общение с вышкой оператора напрямую, минуя внутренний стек модуля, что позволяет значительно ускорить обмен данными.
Пример PPP-пакета:
Каждый пакет PPP начинается и заканчивается символом ~ (0x7E). Протокол поддерживает аутентификацию соединения, шифрование и сжатие данных, по этому довольно сложен для написания собственного решения. Логичнее использовать готовый стек, поддерживающий PPP, например LwIP. Он поддерживает PPPOS и PPPOE (Over serial и Ethernet), протоколы аутентификации PAP и CHAP, имеет хорошую репутацию и широко распространен.
Демо проект
Блок-схема:
Примеры разрабатывались для микроконтроллера STM32 под FreeRTOS.
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
// Настройка gsm, периферии модуля и LwIP
InitGsmUart();
// Создание задачи по приему-отправке пакета и установки соединения
xTaskCreate(StartThread, "Start", configMINIMAL_STACK_SIZE*2, 0, tskIDLE_PRIORITY+1, &taskInitHandle);
// Старт процессов
osKernelStart(NULL, NULL);
while (1) {}
}
void StartThread(void * argument) {
gsmTaskInit();
// подлючение, отправка/прием данных
xTaskCreate(connectTask, "connectTask", configMINIMAL_STACK_SIZE*1, 0, tskIDLE_PRIORITY+1, NULL);
// После удаляем задачу
vTaskDelete(NULL);
}
// Кол-во соединений. В этом примере 1, может быть больше, ограничено размером RAM контроллера и возможностями LwIP (последний легко настраивается)
#define GSM_MAX_CONNECTION 1
// Структура для работы с данными
typedef struct{
uint8_t *rxBuff;
uint16_t rxLen;
}sBuff[GSM_MAX_CONNECTION];
sBuff buff = {0};
void connectTask(void *pServiceNum) {
bool connectState = false;
eRetComm status = eError;
uint16_t delay = 0;
uint8_t serviceNum = *(uint8_t*)pServiceNum;
xSemaphoreHandle xRxPppData; // семафор на прием от ppp
xRxPppData = GsmLLR_GetRxSemphorePoint(serviceNum);
for(;;) {
/* Основной код задачи */
if(connectState == true) {
//Если есть подключение к серверу
while(GsmLLR_ConnectServiceStatus(serviceNum) == eOk) {
//Читаем данные в буфер
buff[serviceNum].rxLen = getRxData(serviceNum, xRxPppData,&(buff[serviceNum].rxBuff));
if(buff[serviceNum].rxLen != 0) {
//Если пришли данные то отправляем обратно
if(GsmLLR_TcpSend(serviceNum, buff[serviceNum].rxBuff,
buff[serviceNum].rxLen) == eOk) {
printf("Connect:#%i SendData OKrn", serviceNum);
}else{
printf("Connect:#%i SendData ERRORrn", serviceNum);
connectState = false;
}
}
}
//Если разрыв соединения
printf("Connect:#%i connection lostrn", serviceNum);
GsmLLR_DisconnectService(serviceNum);
connectState = false;
delay = 1000;
} else {
// соединение закрыто, настраиваем
printf("Connect:#%i connecting...", serviceNum);
// Устанавливаем соединение
if(GsmLLR_ConnectService(serviceNum) == eOk) {
printf("Connect:#%i connected", serviceNum);
connectState = true;
} else { // не получилось сконнектиться
printf("Connect:#%i ERROR", serviceNum);
delay = GSM_CONNECTION_ERROR_DELAY;
connectState = false;
}
}
vTaskDelay(delay/portTICK_RATE_MS);
}
}
// Прием данных
uint16_t getRxData(uint8_t serviceNum, xSemaphoreHandle xRxPppData, uint8_t **ppBufPacket) {
uint16_t retLen = 0;
uint16_t size = 0;
if(xSemaphoreTake(xRxPppData, 1000/portTICK_PERIOD_MS) == pdTRUE) {
size = gsmLLR_TcpGetRxCount(serviceNum);
if(size > 1512) {
retLen = 0;
}else {
retLen = GsmLLR_TcpReadData(serviceNum, ppBufPacket, size);
}
}
return retLen;
}
void gsmTaskInit(void) {
xTaskCreate(vGsmTask, "GSM", configMINIMAL_STACK_SIZE*2, 0, tskIDLE_PRIORITY+1, &gsmInitTaskId);
while((!gsmState.init) || (!pppIsOpen)) {vTaskDelay(100/portTICK_PERIOD_MS);}
}
/* Задача инициализации и управления GSM модулем */
void vGsmTask( void * pvParameters ) {
// никзкоуровневые инициализации
GsmLLR_Init();
GsmLLR2_Init();
GsmPPP_Init();
// пока переферия не готова
while((gsmState.initLLR != true) && (gsmState.initLLR2 != true)){};
if(GsmLLR_PowerUp() != eOk) {
GsmLLR_ModuleLost();
}
for(;;) {
// инициализация
if(gsmState.init == false) {
// если модуль перестал отвечать
if(gsmState.notRespond == true) {
printf("GSM: INIT Module lostrn");
GsmLLR_ModuleLost();
continue;
}
// готовность модуля
if(GsmLLR_ATAT() != eOk) {
gsmState.notRespond = true;
continue;
}
// отключение предупреждений по питанию
if(GsmLLR_WarningOff() != eOk) {
gsmState.notRespond = true;
continue;
}
// настройки ответа
if(GsmLLR_FlowControl() != eOk) {
gsmState.notRespond = true;
continue;
}
// читаем IMEI
if(GsmLLR_GetIMEI(aIMEI) != eOk) {
gsmState.notRespond = true;
continue;
}
DBGInfo("GSM: module IMEI=%srn", aIMEI);
// читаем IMSI
if(GsmLLR_GetIMSI(aIMSI) != eOk) {
gsmState.notRespond = true;
continue;
}
printf("GSM: module IMSI=%srn", aIMSI);
// Версия Software
if(GsmLLR_GetModuleSoftWareVersion(aVerionSoftware) != eOk) {
gsmState.notRespond = true;
continue;
}
// вывод сообщения о регистрации сети (URC)
if(GsmLLR_AtCREG() != eOk) {
gsmState.notRespond = true;
continue;
}
printf("GSM: CREG OKrn");
// читаем уровень сигнала
if(GsmLLR_UpdateCSQ(&gsmCsqValue) != eOk) {
printf("GSM: Get CSQ ERROR, -RELOADrn");
gsmState.notRespond = true;
continue;
}else{
printf("GSM: CSQ value %drn", gsmCsqValue);
// формат SMS
if(GsmLLR_SmsModeSelect(sms_TEXT) != eOk) {
gsmState.notRespond = true;
continue;
}
//удаляем sms
vTaskDelay(DELAY_REPLY_INIT/portTICK_RATE_MS);
if(GsmLLR_SmsClearAll() != eOk) {
printf("GSM: clear SMS ERROR, -RELOADrn");
gsmState.notRespond = true;
continue;
}
printf("GSM: Clear SMS Okrn");
printf("GSM: INIT PPPPrn");
if(GsmLLR_StartPPP(&connectionSettings.gsmSettings) == eOk) {
printf("GSM: INIT PPPP - PPP RUNrn");
xQueueReset(uartParcerStruct.uart.rxQueue);
uartParcerStruct.ppp.pppModeEnable = true;
uartParcerStruct.uart.receiveState = true;
gsmState.init = true;
}else{
printf("GSM: INIT PPPP - PPP ERROR!!!rn");
gsmState.notRespond = true;
continue;
}
}
}
vTaskDelay(1000/portTICK_RATE_MS);
}
}
Поднятие PPP.
Для начала сессии используется 4 команды — comPPP_0-4. Как они отправляются и разбирается ответ мы не рассматриваем, это тема для отдельной статьи. Рассмотрим лишь в общем виде:
char *comPPP_0[] = {"AT+CGDCONT=1,"IP","};
char *comPPP_2[] = {"AT+CGQMIN=1,0,0,0,0,0"};
char *comPPP_3[] = {"AT+CGQREQ=1,2,4,3,6,31"};
char *comPPP_4[] = {"ATD*99***1#"};
eRetComm GsmLLR_StartPPP(sGsmSettings *pSettings) {
printf("StartPPPrn");
sResultCommand resultCommand;
char **comPPP_Mass[3] = {comPPP_2, comPPP_3, comPPP_4};
uint8_t *pData = NULL;
if(GsmLLR_GetMutex() == true) {
pData = pvPortMalloc(GSM_MALLOC_COMMAND_SIZE);
if(pData != NULL) {
memset(pData, 0, GSM_MALLOC_COMMAND_SIZE);
sprintf((char*)pData, "%s%s", comPPP_0[0], (char*)pSettings->gprsApn);
RunAtCommand((char*)pData, &resultCommand);
// Счетчик команд, пока не отправили все
uint8_t stepIndex = 0;
while(stepIndex != (3)) {
uint16_t len = strlen((char*)*comPPP_Mass[stepIndex]);
sprintf((char*)pData, "%s", (char*)*comPPP_Mass[stepIndex]);
RunAtCommand((char*)pData, &resultCommand);
stepIndex++;
}
memset(pData, 0, GSM_MALLOC_COMMAND_SIZE);
vPortFree(pData);
}
GsmLLR_GiveMutex();
}
return eOk;
}
Из кода задачи vGsmTask, следуют, что в случае успешного выполнения «GsmLLR_StartPPP» — выставляется флаг pppModeEnable и очищается очередь uartParcerStruct.uart.rxQueue. Флаг pppModeEnable отображает текущий режим модуля. Обмен между прерыванием UART и стеком/разборщиком команд — идет через очередь.
void GsmPPP_Tsk(void *pvParamter) {
int timeout = 0;
uint8_t i;
bool stateInit = false;
uint16_t tskStackInit;
LwipStack_Init();
pppInit();
pppSetAuth(PPPAUTHTYPE_CHAP, connectionSettings.gsmSettings.gprsUser, connectionSettings.gsmSettings.gprsPass);
sioWriteSemaphore = xSemaphoreCreateBinary();
for(i=0; i<GSM_MAX_CONNECTION; i++) {
connectionPppStruct.semphr[i] = xSemaphoreCreateBinary();
connectionPppStruct.rxData[i].rxSemh = xSemaphoreCreateBinary();
}
for(;;) {
// Если выставлен флаг о использовании PPP и все было настроено
if(uartParcerStruct.ppp.pppModeEnable == true) {
if(!pppIsOpen) {
pppNumport = pppOverSerialOpen(0, linkStatusCB, &pppIsOpen);
pppStop = 0;
timeout = 0;
stateInit = false;
while(timeout < 300) {
if(pppIsOpen) {
printf("PPP init - OKrn");
lwip_stats.link.drop = 0;
lwip_stats.link.chkerr = 0;
lwip_stats.link.err = 0;
stateInit = true;
break;
}else{
timeout ++;
vTaskDelay(100/portTICK_RATE_MS);
}
}
if(stateInit != true) {
printf("PPP init - TIMEOUT-ERRORrn");
pppClose(pppNumport);
pppIsOpen = false;
uartParcerStruct.ppp.pppModeEnable = false;
gsmState.init = false;
gsmState.notRespond = true;
}
}else{
if((lwip_stats.link.drop !=0) || (lwip_stats.link.chkerr !=0)) {
lwip_stats.link.drop = 0;
lwip_stats.link.chkerr = 0;
printf("GSMM: DROPING FAIL!!! RESTART PPPrn");
for(i=0; i<SERVERS_COUNT; i++) {
GsmPPP_Disconnect(i);
}
pppClose(pppNumport);
pppIsOpen = false;
uartParcerStruct.ppp.pppModeEnable = false;
gsmState.init = false;
gsmState.notRespond = true;
vTaskDelay(500/portTICK_PERIOD_MS);
}
}
}
vTaskDelay(500/portTICK_RATE_MS);
}
}
Общие функции, работающие с PPP
bool GsmPPP_Connect(uint8_t numConnect, char *pDestAddr, uint16_t port) {
struct ip_addr resolved = {0};
bool useDns = false;
uint8_t ipCut[4] = {0};
if(!pppIsOpen) {
printf("GSMPPP: CONNECT ERROR - PPP closedrn");
return false;
}
sscanf(pDestAddr, "%i.%i.%i.%i", &ipCut[0], &ipCut[1], &ipCut[2], &ipCut[3]);
if((ipCut[0]!=0)&&(ipCut[1]!=0)&&(ipCut[2]!=0)&&(ipCut[3]!=0)) {
IP4_ADDR(&connectionPppStruct.ipRemoteAddr[numConnect], ipCut[0],ipCut[1],ipCut[2],ipCut[3]); //31,10,4,158);
useDns = false;
}else{
useDns = true;
}
if(connectionPppStruct.connected[numConnect] == false) {
connectionPppStruct.tcpClient[numConnect] = tcp_new(); // create tcpPcb
tcp_recv(connectionPppStruct.tcpClient[numConnect], server_recv);
if(useDns == true) {
switch(dns_gethostbyname(pDestAddr, &resolved, destServerFound, &numConnect)) {
case ERR_OK: // numeric or cached, returned in resolved
connectionPppStruct.ipRemoteAddr[numConnect].addr = resolved.addr;
break;
case ERR_INPROGRESS: // need to ask, will return data via callback
if(xSemaphoreTake(connectionPppStruct.semphr[numConnect], 10000/portTICK_PERIOD_MS) != pdTRUE) {
while(tcp_close(connectionPppStruct.tcpClient[numConnect]) != ERR_OK) { vTaskDelay(100/portTICK_PERIOD_MS); }
connectionPppStruct.connected[numConnect] = false;
printf("GSMPPP: dns-ERRORrn");
return false;
}else{ }
break;
}
}
tcp_connect(connectionPppStruct.tcpClient[numConnect], &connectionPppStruct.ipRemoteAddr[numConnect], port, &TcpConnectedCallBack);
if(xSemaphoreTake(connectionPppStruct.semphr[numConnect], 10000/portTICK_PERIOD_MS) == pdTRUE) {
connectionPppStruct.connected[numConnect] = true;
printf("GSMPPP: connected %srn", inet_ntoa(connectionPppStruct.ipRemoteAddr));
return true;
}else{
tcp_abort(connectionPppStruct.tcpClient[numConnect]);//tcp_close(connectionPppStruct.tcpClient[numConnect]);
while(tcp_close(connectionPppStruct.tcpClient[numConnect]) != ERR_OK) { vTaskDelay(100/portTICK_PERIOD_MS); }
printf("GSMPPP: connectTimeout-ERRORrn");
return false;
}
}else{
if(GsmLLR_ConnectServiceStatus(numConnect) == eOk) {
printf("GSMPPP: CONNECT-already connected %srn", inet_ntoa(connectionPppStruct.ipRemoteAddr));
return true;
}else{
printf("GSMPPP: CONNECT CLOSE!!!rn");
return false;
}
}
return false;
}
bool GsmPPP_Disconnect(uint8_t numConnect) {
if(!pppIsOpen) {
printf("GSMPPP: CONNECT ERROR - PPP closedrn");
return false;
}
if(connectionPppStruct.tcpClient[numConnect] == NULL) {
return false;
}
while(tcp_close(connectionPppStruct.tcpClient[numConnect]) != ERR_OK) { vTaskDelay(100/portTICK_PERIOD_MS); }
connectionPppStruct.connected[numConnect] = false;
return true;
}
bool GsmPPP_ConnectStatus(uint8_t numConnect) {
if(!pppIsOpen) {
printf("GSMPPP: CONNECT ERROR - PPP closedrn");
return false;
}
if(connectionPppStruct.tcpClient[numConnect]->state == ESTABLISHED) {
return true;
}
return false;
}
bool GsmPPP_SendData(uint8_t numConnect, uint8_t *pData, uint16_t len) {
if(!pppIsOpen) {
printf("GSMPPP: CONNECT ERROR - PPP closedrn");
return false;
}
if(tcp_write(connectionPppStruct.tcpClient[numConnect], pData, len, NULL) == ERR_OK) {
return true;
}else {
while(tcp_close(connectionPppStruct.tcpClient[numConnect]) != ERR_OK) { vTaskDelay(100/portTICK_PERIOD_MS); }
connectionPppStruct.connected[numConnect] = false;
connectionPppStruct.rxData[numConnect].rxBufferLen = 0;
memset(connectionPppStruct.rxData[numConnect].rxBuffer,0, sizeof(connectionPppStruct.rxData[numConnect].rxBuffer));
}
return false;
}
uint16_t GsmPPP_GetRxLenData(uint8_t numConnect) {
if(!pppIsOpen) {
printf("GSMPPP: CONNECT ERROR - PPP closedrn");
return false;
}
return connectionPppStruct.rxData[numConnect].rxBufferLen;
}
uint16_t GsmPPP_ReadRxData(uint8_t numConnect, uint8_t **ppData) {
if(!pppIsOpen) {
printf("GSMPPP: CONNECT ERROR - PPP closedrn");
return false;
}
if(connectionPppStruct.rxData[numConnect].rxBufferLen != 0) {
*ppData = (uint8_t *) connectionPppStruct.rxData[numConnect].rxBuffer;
uint16_t retLen = connectionPppStruct.rxData[numConnect].rxBufferLen;
connectionPppStruct.rxData[numConnect].rxBufferLen = 0;
return retLen;
}
return false;
}
static void destServerFound(const char *name, struct ip_addr *ipaddr, void *arg)
{
uint8_t *num = (uint8_t*)arg;
if(*num < SERVERS_COUNT) {
printf("GSMPPP: DEST FOUND %srn", inet_ntoa(ipaddr->addr));
connectionPppStruct.ipRemoteAddr[*num].addr = ipaddr->addr;
xSemaphoreGive(connectionPppStruct.semphr[*num]);
}else{
printf("GSMPPP: DNS != SERVER%srn", inet_ntoa(ipaddr->addr));
}
}
static err_t TcpConnectedCallBack(void *arg, struct tcp_pcb *tpcb, err_t err) {
for(uint8_t i=0; i<SERVERS_COUNT; i++) {
if(tpcb == connectionPppStruct.tcpClient[i]) {
printf("GSMPPP: connected (callback)%srn", inet_ntoa(tpcb->local_ip.addr));
xSemaphoreGive(connectionPppStruct.semphr[i]);
break;
}
}
}
static err_t server_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
{
LWIP_UNUSED_ARG(arg);
if(err == ERR_OK && p != NULL) {
tcp_recved(pcb, p->tot_len);
printf("GSMPPP:server_recv(): pbuf->len %d byten [%s]", p->len, inet_ntoa(pcb->remote_ip.addr));
for(uint8_t i=0; i<SERVERS_COUNT; i++) {
if(pcb->remote_ip.addr == connectionPppStruct.tcpClient[i]->remote_ip.addr) {
printf("GSMPPP: server_recv (callback) [%s]rn", inet_ntoa(pcb->remote_ip.addr));
if(p->len < sizeof(connectionPppStruct.rxData[i].rxBuffer)) {
memcpy(connectionPppStruct.rxData[i].rxBuffer, p->payload, p->len);
connectionPppStruct.rxData[i].rxBufferLen = p->len;
xSemaphoreGive(connectionPppStruct.rxData[i].rxSemh);
printf("GSMPPP: server_recv (callback) GIVE SEMPH[%s][%d]rn", inet_ntoa(pcb->remote_ip.addr), p->len);
}else{
printf("GSMPPP: server_recv p->len > sizeof(buf) -ERRORrn");
}
}
}
pbuf_free(p);
}else{
printf("nserver_recv(): Errors-> ");
if (err != ERR_OK)
printf("1) Connection is not on ERR_OK state, but in %d state->n", err);
if (p == NULL)
printf("2) Pbuf pointer p is a NULL pointer->n ");
printf("server_recv(): Closing server-side connection...");
pbuf_free(p);
server_close(pcb);
}
return ERR_OK;
}
xSemaphoreHandle * GsmPPP_GetRxSemaphorePoint(uint8_t numService) {
return (connectionPppStruct.rxData[numService].rxSemh);
}
static err_t server_poll(void *arg, struct tcp_pcb *pcb)
{
static int counter = 1;
LWIP_UNUSED_ARG(arg);
LWIP_UNUSED_ARG(pcb);
printf("nserver_poll(): Call number %dn", counter++);
return ERR_OK;
}
static err_t server_err(void *arg, err_t err)
{
LWIP_UNUSED_ARG(arg);
LWIP_UNUSED_ARG(err);
printf("nserver_err(): Fatal error, exiting...n");
return ERR_OK;
}
static void server_close(struct tcp_pcb *pcb)
{
tcp_arg(pcb, NULL);
tcp_sent(pcb, NULL);
tcp_recv(pcb, NULL);
while(tcp_close(pcb) != ERR_OK) { vTaskDelay(100/portTICK_PERIOD_MS); }
for(uint8_t i=0; i<SERVERS_COUNT; i++) {
if(pcb == connectionPppStruct.tcpClient[i]) {
printf("GSMPPP: server_close (callback)%srn", inet_ntoa(pcb->local_ip.addr));
connectionPppStruct.connected[i] = false;
}else{
printf("GSMPPP: server_recv p->len > sizeof(buf) -ERRORrn");
}
}
}
// Прослойка на чтение из очереди - отправка данных в LwIP
u32_t sio_read(sio_fd_t fd, u8_t *data, u32_t len)
{
unsigned long i = 0;
if(uartParcerStruct.ppp.pppModeEnable) {
while(xQueueReceive(uartParcerStruct.uart.rxQueue,&data[i], 0) == pdTRUE) {
if(i==0) {
printf("Reading PPP packet from UARTrn");
}
printf("%0.2x ", data[i]);
i++;
if (pppStop||(i==len)) {
pppStop = false;
return i;
}
}
if (i>0) {
printf("n");
}
}
return i;
}
// Запись из LwIP в UART (GSM)
u32_t sio_write(sio_fd_t fd, u8_t *data, u32_t len)
{
u32_t retLen = 0;
if(uartParcerStruct.ppp.pppModeEnable) {
if(HAL_UART_Transmit_IT(&huart3, data, len) == HAL_OK) {
xSemaphoreTake(sioWriteSemaphore, portMAX_DELAY);
retLen = len;
}else{
printf("HAL ERRROR WRITE [sio_write]rn");
}
}else{
printf("sio_write not in PPP mode!rn");
}
return retLen;
}
// Прерывание сессии, очистка очереди
void sio_read_abort(sio_fd_t fd)
{
pppStop = true;
xQueueReset(uartParcerStruct.uart.rxQueue);
}
u32_t sys_jiffies(void) {
return xTaskGetTickCount();
}
// Калбек вызывается после успешной или не успешной попытки соединения
static void linkStatusCB(void * ctx, int errCode, void * arg) {
printf("GSMPP: linkStatusCBrn"); /* just wait */
bool *connected = (bool*)ctx;
struct ppp_addrs * addrs = arg;
switch (errCode) {
case PPPERR_NONE: { /* We are connected */
printf("ip_addr = %srn", inet_ntoa(addrs->our_ipaddr));
printf("netmask = %srn", inet_ntoa(addrs->netmask));
printf("dns1 = %srn", inet_ntoa(addrs->dns1));
printf("dns2 = %srn", inet_ntoa(addrs->dns2));
*connected = 1;
break;
}
case PPPERR_CONNECT: {
printf("lost connectionrn"); /* just wait */
*connected = 0;
break;
}
default: { /* We have lost connection */
printf("connection errorrn"); /* just wait */
*connected = 0;
break;
}
}
}
Отправляем данные через TCP терминал:
В структурах видно пришедший пакет:
Подведем итоги.
Отказавшись от AT-команд, удалось значительно уменьшить и упростить код по разбору и отправки команд, сложного (потенциально не надежного) разбора ответов, приема данных и URC кодов. AT-команды остались для первоначальной настройки модема и записи параметров APN.
Записанную логическим анализатором сессию PPP, для подробного изучения, можно взять здесь.
Автор: Khomin