В этот раз будем учиться делать странное, а именно отправлять USSD-запрос оператору мобильной связи с помощью TAPI в Windows Mobile.

USSD (Unstructured Supplementary Service Data) — стандартный сервис в сетях GSM, позволяющий организовать интерактивное взаимодействие между абонентом сети и сервисным приложением в режиме передачи коротких сообщений. – Wikipedia

На самом деле USSD-запросы это то, чем каждый из нас пользуется довольно часто, например, при пополнении счета или при запросе состояния счета у оператора мобильной связи.

Примеры USSD-запросов:

  • *101#
  • *100*111122223333444455#

Вот именно такой финт ушами мы и будем пытаться реализовать с помощью программных средств.

Долгих вступлений не будет, а будет сразу практика с простеньким примером. Рассмотрим простейшее приложение, которое отправляет запрос состояния счета мобильному опреатору.

Для начала нам надо инициализировать TAPI.

#include <windows.h>
#include <tapi.h>
#include <extapi.h>
#include <tsp.h>

#define EXT_API_LOW_VERSION     0x00010000
#define EXT_API_HIGH_VERSION    0x00010000

long InitializeTAPI(HLINEAPP & lineApp, DWORD & lowAPIVersion, DWORD & deviceCount)
{
	LINEINITIALIZEEXPARAMS	lineParams;
	ZeroMemory(&lineParams,sizeof(LINEINITIALIZEEXPARAMS));

	lineParams.dwTotalSize	= sizeof(LINEINITIALIZEEXPARAMS);
	lineParams.dwOptions	= LINEINITIALIZEEXOPTION_USEHIDDENWINDOW; 

	lowAPIVersion = TAPI_CURRENT_VERSION;

	return lineInitializeEx(&lineApp,
		(HINSTANCE)GetModuleHandle(NULL),
		LineCallback,
		L"wxUSSDRequest",
		&deviceCount,
		&lowAPIVersion,
		&lineParams);
}

LineCallback – это callback-функция, которая будет вызываться при наступлении событий TAPI.

void FAR PASCAL LineCallback(DWORD hDevice,
							 DWORD dwMsg,
							 DWORD dwCallbackInstance,
							 DWORD dwParam1,
							 DWORD dwParam2,
							 DWORD dwParam3)
{
	switch(dwMsg)
	{
	case LINE_DEVSPECIFIC:
		{
			if(dwParam1 == LINE_USSD)
			{
				DWORD	dwMessageId	= dwParam2;
			}

			break;
		}
	case LINE_REPLY:
		{
			if(dwParam2 == 0) return;
			wprintf(L"Error: ");	
			switch(dwParam2)
			{
			case LINEERR_INVALLINEHANDLE:		wprintf(L"LINEERR_INVALLINEHANDLE");	break;
			case LINEERR_NOMEM:					wprintf(L"LINEERR_NOMEM");				break;
			case LINEERR_OPERATIONUNAVAIL:		wprintf(L"LINEERR_OPERATIONUNAVAIL");	break;
			case LINEERR_OPERATIONFAILED:		wprintf(L"LINEERR_OPERATIONFAILED");	break;
			case LINEERR_RESOURCEUNAVAIL:		wprintf(L"LINEERR_RESOURCEUNAVAIL");	break;
			case LINEERR_INVALPOINTER:			wprintf(L"LINEERR_INVALPOINTER");		break;
			case LINEERR_INVALPARAM:			wprintf(L"LINEERR_INVALPARAM");			break;
			case LINEERR_UNINITIALIZED:			wprintf(L"LINEERR_UNINITIALIZED");		break;
			default:							wprintf(L"Error: %x",dwParam2);			break;
			}
			wprintf(L"\r\n");
			break;
		}
	}
}

Затем нужно получить идентификатор телефонной линии. Сделать это можно с помощью функции lineGetDevCaps.

long GetCellularLineID(HLINEAPP lineApp,
					   DWORD lowAPIVersion,
					   DWORD deviceCount,
					   DWORD & apiVersion)
{
	DWORD				dwReturn		= 0xFFFFFFFF;
	long				lResult			= 0;
	LINEEXTENSIONID		sLineExt		= {0};
	LPLINEDEVCAPS		lpLineDevCaps	= NULL;	
	BOOL				bContinue		= TRUE;

	for(DWORD dwLine = 0; dwLine < deviceCount && bContinue; ++dwLine)
	{
		lResult		= lineNegotiateAPIVersion(lineApp,
			dwLine,
			lowAPIVersion,
			TAPI_CURRENT_VERSION,
			&apiVersion,
			&sLineExt);

		if(0 == lResult)
		{
			lpLineDevCaps	= (LPLINEDEVCAPS)LocalAlloc(LPTR,sizeof(LINEDEVCAPS));
			lResult			= LINEERR_STRUCTURETOOSMALL;

			lpLineDevCaps->dwTotalSize	= sizeof(LINEDEVCAPS);
			lpLineDevCaps->dwNeededSize	= sizeof(LINEDEVCAPS);

			while(LINEERR_STRUCTURETOOSMALL == lResult)
			{
				lResult	= lineGetDevCaps(lineApp,dwLine,TAPI_CURRENT_VERSION,0,lpLineDevCaps);

				if(LINEERR_STRUCTURETOOSMALL == lResult || lpLineDevCaps->dwTotalSize < lpLineDevCaps->dwNeededSize)
				{
					lpLineDevCaps	= (LPLINEDEVCAPS)LocalReAlloc(lpLineDevCaps,lpLineDevCaps->dwNeededSize,LMEM_MOVEABLE);
					lResult			= LINEERR_STRUCTURETOOSMALL;

					lpLineDevCaps->dwTotalSize	= lpLineDevCaps->dwNeededSize;
				}
			}

			if(0 == lResult)
			{
				TCHAR szName[512];

				memcpy((PVOID)szName,(PVOID)((BYTE*)lpLineDevCaps + lpLineDevCaps ->dwLineNameOffset), 
					lpLineDevCaps->dwLineNameSize);

				szName[lpLineDevCaps->dwLineNameSize]	= 0;

				if(_tcscmp(szName,CELLTSP_LINENAME_STRING) == 0)
				{
					dwReturn	= dwLine;
					bContinue	= FALSE;
				}
			}

			LocalFree((HLOCAL)lpLineDevCaps);
		}
	}

	return dwReturn;
}

Теперь нужно открыть телефонную линию для последующей работы с ней. Делается это с помощью функции lineOpen.

HLINE OpenTAPILine(HLINEAPP lineApp, DWORD cellularID, DWORD apiVersion)
{
	DWORD	dwMediaMode = LINEMEDIAMODE_INTERACTIVEVOICE;
	HLINE	hLine		= NULL;
	DWORD	extVersion	= 0;
	long	lReturn		= lineOpen(lineApp, 
		cellularID, 
		&hLine,
		TAPI_CURRENT_VERSION, 0,
		(DWORD)NULL,
		LINECALLPRIVILEGE_OWNER,
		dwMediaMode, 0);

	lReturn = ::lineNegotiateExtVersion(lineApp,
		cellularID,
		apiVersion, 
		EXT_API_LOW_VERSION, 
		EXT_API_HIGH_VERSION, 
		&extVersion);

	return hLine;
}

После завершения работы с TAPI необходимо обязательно закрыть все хэндлы.

void ShutdownTAPI(HLINE cellularLine, HLINEAPP lineApp)
{
	if(cellularLine)
	{
		lineClose(cellularLine);
	}

	if(lineApp)
	{
		lineShutdown(lineApp);
	}
}

И вот мы добрались до самого интересного, а именно до того момента, когда нам все предыдущие части кода нужно использовать для достижения конечной цели – зохвата мира отправки запроса.

int wmain()
{
	HLINEAPP lineApp = NULL;
	DWORD lowAPIVersion = 0;
	DWORD apiVersion = 0;
	DWORD deviceCount = 0;
	DWORD cellularID = 0;
	HLINE cellularLine = NULL;
	TCHAR command[] = _T("*101#");
	int errorCode;

	do
	{
		if(InitializeTAPI(lineApp, lowAPIVersion, deviceCount) != 0) 
		{
			errorCode = 1;
			break;
		}
		cellularID = GetCellularLineID(lineApp, lowAPIVersion, deviceCount, apiVersion);
		if(cellularID == 0xFFFFFFFF)
		{
			errorCode = 2;
			break;
		}
		cellularLine = OpenTAPILine(lineApp, cellularID, apiVersion);
		if(cellularLine == NULL)
		{
			errorCode = 3;
			break;
		}
		// Супер-строчка, которая отправляет USSD-запрос !!! Уиииии!
		if(lineSendUSSD(cellularLine, (const BYTE* const)command, sizeof(command), 0) < 0)
		{
			errorCode = 4;
			break;
		}
		return 0;
	}
	while(false);
	ShutdownTAPI(cellularLine, lineApp);
	return errorCode;
}
&#91;/sourcecode&#93;
И вот, вызов <a href="http://msdn.microsoft.com/en-us/library/ms862581.aspx" title="Отправка USSD запроса">lineSendUSSD</a> - это то к чему мы так стремились. Функция принимает в качестве параметров хэндл телефонной линии, строку запроса и длину запроса. В случае успешного завершения работы возвращается нулевой результат. В случае ошибки возвращается отрицательное значение.

И в качестве дополнения ко всему сказанному выше, класс-обертка для wxWinCE:

<strong>wxUSSDRequest.h</strong>

#ifndef _WX_USSD_REQUEST_H
#define _WX_USSD_REQUEST_H

#include <wx/wx.h>
#include <tapi.h>
#include <extapi.h>
#include <tsp.h>

class wxUSSDRequest
{
	long InitializeTAPI(DWORD & lowAPIVersion,
		DWORD & deviceCount);
	long GetCellularLineID(DWORD lowAPIVersion,
		DWORD deviceCount,
		DWORD & apiVersion);
	HLINE OpenTAPILine(DWORD cellularID, DWORD apiVersion);

	void ShutdownTAPI();

	static void FAR PASCAL LineCallback(DWORD hDevice,
		DWORD dwMsg,
		DWORD dwCallbackInstance,
		DWORD dwParam1,
		DWORD dwParam2,
		DWORD dwParam3);
public:
	wxUSSDRequest();
	~wxUSSDRequest();

	bool SendUSSDCommand(const TCHAR * command, DWORD length);
	int Init();
	bool IsOK();
private:
	HLINEAPP m_LineApp;
	HLINE m_CellularLine;
	bool m_IsOK;
};

#endif

wxUSSDRequest.cpp

#include "wxUSSDRequest.h"

#define EXT_API_LOW_VERSION     0x00010000
#define EXT_API_HIGH_VERSION    0x00010000

wxUSSDRequest::wxUSSDRequest()
: m_LineApp(NULL), m_CellularLine(NULL), m_IsOK(false)
{
	m_IsOK = (Init() == 0);
}

wxUSSDRequest::~wxUSSDRequest()
{
	ShutdownTAPI();
}

int wxUSSDRequest::Init()
{
	int result(0);
	do
	{
		DWORD lowAPIVersion(0);
		DWORD deviceCount(0);
		if(InitializeTAPI(lowAPIVersion, deviceCount) != 0) 
		{
			result = 1;
			break;
		}
		DWORD apiVersion(0);
		DWORD cellularID = GetCellularLineID(lowAPIVersion, deviceCount, apiVersion);
		if(cellularID == 0xFFFFFFFF) 
		{
			result = 2;
			break;
		}
		m_CellularLine = OpenTAPILine(cellularID, apiVersion);
		if(m_CellularLine == NULL) 
		{
			result = 3;
			break;
		}
	}
	while(false);
	return result;
}

bool wxUSSDRequest::IsOK()
{
	return m_IsOK;
}

bool wxUSSDRequest::SendUSSDCommand(const TCHAR * command, DWORD length)
{
	if(lineSendUSSD(m_CellularLine, (const BYTE* const)command, length, 0) < 0)
	{
		return false;
	}
	return true;
}

long wxUSSDRequest::InitializeTAPI(DWORD & lowAPIVersion, DWORD & deviceCount)
{
	LINEINITIALIZEEXPARAMS	lineParams;
	ZeroMemory(&lineParams,sizeof(LINEINITIALIZEEXPARAMS));

	lineParams.dwTotalSize	= sizeof(LINEINITIALIZEEXPARAMS);
	lineParams.dwOptions	= LINEINITIALIZEEXOPTION_USEHIDDENWINDOW; 

	lowAPIVersion = TAPI_CURRENT_VERSION;

	return lineInitializeEx(&m_LineApp,
		(HINSTANCE)wxTheApp->GetInstance(),
		LineCallback,
		wxT("wxUSSDRequest"),
		&deviceCount,
		&lowAPIVersion,
		&lineParams);
}

void wxUSSDRequest::ShutdownTAPI()
{
	if(m_CellularLine)
	{
		lineClose(m_CellularLine);
	}

	if(m_LineApp)
	{
		lineShutdown(m_LineApp);
	}

	m_LineApp		= NULL;
	m_CellularLine	= NULL;
}

long wxUSSDRequest::GetCellularLineID(DWORD lowAPIVersion,
									  DWORD deviceCount,
									  DWORD & apiVersion)
{
	DWORD				dwReturn		= 0xFFFFFFFF;
	long				lResult			= 0;
	LINEEXTENSIONID		sLineExt		= {0};
	LPLINEDEVCAPS		lpLineDevCaps	= NULL;	
	BOOL				bContinue		= TRUE;

	for(DWORD dwLine = 0; dwLine < deviceCount && bContinue; ++dwLine)
	{
		lResult		= lineNegotiateAPIVersion(m_LineApp,
			dwLine,
			lowAPIVersion,
			TAPI_CURRENT_VERSION,
			&apiVersion,
			&sLineExt);

		if(0 == lResult)
		{
			lpLineDevCaps	= (LPLINEDEVCAPS)LocalAlloc(LPTR,sizeof(LINEDEVCAPS));
			lResult			= LINEERR_STRUCTURETOOSMALL;

			lpLineDevCaps->dwTotalSize	= sizeof(LINEDEVCAPS);
			lpLineDevCaps->dwNeededSize	= sizeof(LINEDEVCAPS);

			while(LINEERR_STRUCTURETOOSMALL == lResult)
			{
				lResult	= lineGetDevCaps(m_LineApp,dwLine,TAPI_CURRENT_VERSION,0,lpLineDevCaps);

				if(LINEERR_STRUCTURETOOSMALL == lResult || lpLineDevCaps->dwTotalSize < lpLineDevCaps->dwNeededSize)
				{
					lpLineDevCaps	= (LPLINEDEVCAPS)LocalReAlloc(lpLineDevCaps,lpLineDevCaps->dwNeededSize,LMEM_MOVEABLE);
					lResult			= LINEERR_STRUCTURETOOSMALL;

					lpLineDevCaps->dwTotalSize	= lpLineDevCaps->dwNeededSize;
				}
			}

			if(0 == lResult)
			{
				TCHAR szName[512];

				memcpy((PVOID)szName,(PVOID)((BYTE*)lpLineDevCaps + lpLineDevCaps ->dwLineNameOffset), 
					lpLineDevCaps->dwLineNameSize);

				szName[lpLineDevCaps->dwLineNameSize]	= 0;

				if(_tcscmp(szName,CELLTSP_LINENAME_STRING) == 0)
				{
					dwReturn	= dwLine;
					bContinue	= FALSE;
				}
			}

			LocalFree((HLOCAL)lpLineDevCaps);
		}
	}

	return dwReturn;
}

HLINE wxUSSDRequest::OpenTAPILine(DWORD cellularID, DWORD apiVersion)
{
	DWORD	dwMediaMode = LINEMEDIAMODE_INTERACTIVEVOICE;
	HLINE	hLine		= NULL;
	DWORD	extVersion	= 0;
	long	lReturn		= lineOpen(m_LineApp, 
		cellularID, 
		&hLine,
		TAPI_CURRENT_VERSION, 0,
		(DWORD)this,
		LINECALLPRIVILEGE_OWNER,
		dwMediaMode, 0);

	lReturn				= ::lineNegotiateExtVersion(m_LineApp,
		cellularID,
		apiVersion, 
		EXT_API_LOW_VERSION, 
		EXT_API_HIGH_VERSION, 
		&extVersion);

	return hLine;
}

void FAR PASCAL wxUSSDRequest::LineCallback(DWORD hDevice,
											DWORD dwMsg,
											DWORD dwCallbackInstance,
											DWORD dwParam1,
											DWORD dwParam2,
											DWORD dwParam3)
{
	switch(dwMsg)
	{
	case LINE_DEVSPECIFIC:
		{
			if(dwParam1 == LINE_USSD)
			{
				DWORD	dwMessageId	= dwParam2;
			}

			break;
		}
	case LINE_REPLY:
		{
			wxString strError;

			switch(dwParam2)
			{
			case 0:								strError	= wxEmptyString;						break;
			case LINEERR_INVALLINEHANDLE:		strError	= wxT("LINEERR_INVALLINEHANDLE");		break;
			case LINEERR_NOMEM:					strError	= wxT("LINEERR_NOMEM");					break;
			case LINEERR_OPERATIONUNAVAIL:		strError	= wxT("LINEERR_OPERATIONUNAVAIL");		break;
			case LINEERR_OPERATIONFAILED:		strError	= wxT("LINEERR_OPERATIONFAILED");		break;
			case LINEERR_RESOURCEUNAVAIL:		strError	= wxT("LINEERR_RESOURCEUNAVAIL");		break;
			case LINEERR_INVALPOINTER:			strError	= wxT("LINEERR_INVALPOINTER");			break;
			case LINEERR_INVALPARAM:			strError	= wxT("LINEERR_INVALPARAM");			break;
			case LINEERR_UNINITIALIZED:			strError	= wxT("LINEERR_UNINITIALIZED");			break;
			default:							strError.Format(_("Error: %x"),dwParam2);			break;
			}

			if(!strError.IsEmpty())
			{
				wxLogError(strError);
			}
			break;
		}
	}
}

Пример использования

wxUSSDRequest * m_USSDRequest;
...
wxUSSDRequest * GetUSSDRequest() const { return m_USSDRequest ; }
...
void wxUSSDRequestMainFrame::OnSENDClick( wxCommandEvent& event )
{
	do
	{
		if(!wxGetApp().GetUSSDRequest() || !wxGetApp().GetUSSDRequest()->IsOK()) break;
		wxString command = m_USSDTextCtrl->GetValue().GetData();
		if(!wxGetApp().GetUSSDRequest()->SendUSSDCommand(command, 
			command.Length() * sizeof(wxChar)))
		{
			wxLogError(_("Error sending USSD message '%s'"),
				m_USSDTextCtrl->GetValue().GetData());
		}
	}
	while(false);
}

Скачать исходный код примера.

Previous ArticleNext Article
Технический директор IT-Dimension, компании-разработчика кросс-платформенного программного обеспечения

This post has 5 Comments

5
  1. ого, а ведь можно просто в ком порт написать:
    AT+CUSD=”*101#”

  2. Можно, но это будет хак. Какая-то у тебя нездоровая любовь к реализации всего вручную 😛

  3. Вобще на самом деле работа с COM-портом не всегда плохо, но если, например, для решения задачи можно использовать API, предоставляемое ОС, то лучше использовать его.

  4. нет, нет, нет я не отрицаю полезности API, но просто киллограмы кода на то что делается 1-й AT командой…

  5. Эээ.. Ну а если руками отправлять AT-командой, это ж все равно надо писать код проверки, на каком порту висит GPRS-модем, это как минимум. Ну и глядя на то, как MS относится к системе безопасности начиная с WM5, вполне возможно что в какой-нибудь из новых версий для прямого доступа к аппаратным устройствам они будут просить чтобы приложение подписано было.
    Там вроде в Windows Mobile Developer Blog анонс был следующих топиков, обещали вроде тему “Deep Dive: RIL APIs vs TAPI”, может в каментах потом чего интересного расскажут, там часто по существу такие полезные каменты бывают

Leave a Reply

Your email address will not be published. Required fields are marked *

В.

Видео-урок: Работа с SQLite в Windows Mobile

Новый скринкаст о том как собрать минимальное приложение для Windows Mobile, работающее с базой данных SQLite.

Из видео можно узнать:

  • Как создать минимальное приложение для Windows Mobile с wxWinCE.
  • Собрать библиотеку SQLite3 для Windows Mobile 5.
  • Собрать библиотеку-обертку wxSQLite3 для работы с базами данных SQLite.
  • Создать базу данных SQLite.
  • Создать таблицы в базе данных SQLite.
  • Сделать выборку данных из таблицы.
  • Обработать исключения, возникающие при ошибках доступа к базам SQLite.
M.

Microsoft официально объявила о выходе Windows Mobile 6.5 и новых сервисов

Компания Microsoft на выставке GSMA Mobile World Congress официально представила новую версию своей мобильной операционной системы Windows Mobile 6.5, а также новые сервисы Windows Marketplace for Mobile и My Phone.

Windows Mobile 6.5 - ScreenshotsИз значимых нововведений можно отметить новый пользовательский интерфейс системы, который претерпел значительных изменений, и позволяет пользовать работать с ним без помощи стилуса, и новую версию браузера –  Internet Explorer 6.

Сервис MyPhone представляет собой онлайн хранилище информации, такой как адресная книга, текстовые сообщения, фото и видео. Пользователь получает возможность синхронизировать информацию своего телефона с веб-сервером и редактировать ее с помощью веб-интерфейса на сайте My Phone.

Сервис Windows Marketplace for Mobile представляет собой аналог Android Market или AppStore от Apple. С его помощью пользователи смогут покупать ПО для Windows Mobile через Internet.

Пока Microsoft не огласила условия, на которых разработчики смогут размещать ПО в Windows Marketplace for Mobile.