Thursday, May 12, 2005

Programatically Turning the Pocket PC WLAN Adapter On and Off

Here is another frequently requested Pocket PC programming trick. With the wide adoption of WiFi / 802.11x wireless for PDAs, Pocket PC developers very frequently want to programatically turn the wireless LAN adapter on their device on or off. There are various reasons for this, not least of which is the desire to exercise greater control over the power consumption of the wireless radio.

The trick to doing this is our friend the DeviceIoControl API. Basically what you need to do is this:

To power off the WLAN radio: Unbind the wireless adapter, then power it off.
To power on the WLAN radio: Power on the adapter, then bind it.

Here is the code:

// wlanutils.cpp : Defines the entry point for the DLL application.
//

#include
#include
#include
#include
#include
#include "wlanutils.h"

///////////////////////////////////////////////////////
//Global definitions

//g_pszAdapterName --> Used for the multisz null terminated adapter string name for the
// IOCTL_NDIS_BIND_ADAPTER and IOCTL_NDIS_UNBIND_ADAPTER ioctls
// By the time this variable is needed, it is in the proper format.
TCHAR* g_pszAdapterName = NULL;
DWORD g_dwAdapterNameLen = 0;
//g_pszDriverName --> Contains the name of the active WLAN driver.
//This value corresponds to the SanDisk SDIO WiFi card.
//The driver name for your device may be different. Look in
//the device registry under HKLM\Drivers for yours.
TCHAR g_pszDriverName[] = TEXT("SDN1:");

//////////////////////////////////////////////////////////
//Public exported functions
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
return TRUE;
}

int GetRadioState()
{
HANDLE hDriver = NULL;
DWORD dwBytesOut = 0;
CEDEVICE_POWER_STATE ps;
int nPowerState = GET_POWER_STATE_ERROR;

hDriver = ::CreateFile(g_pszDriverName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL);
if (INVALID_HANDLE_VALUE != hDriver)
{
BOOL bRes = ::DeviceIoControl(hDriver, IOCTL_POWER_GET, NULL, 0, &ps, sizeof(ps), &dwBytesOut, NULL);
if (bRes) {
nPowerState = ((ps == D4)?GET_POWER_STATE_OFF:GET_POWER_STATE_ON); //Note: This treats all states but off as on, i.e, stanby is considered on
}
::CloseHandle(hDriver);
}
return (nPowerState);
}

//bPowerState paramter is TRUE to turn radio on, FALSE to turn off
BOOL PowerRadio(BOOL bPowerState)
{
HANDLE hNdis = NULL;
HANDLE hDriver = NULL;
BOOL bRes = FALSE;
DWORD dwBytesOut = 0;
DWORD dwError = 0;
CEDEVICE_POWER_STATE ps;

GetAdapterName();

ps = (bPowerState?D0:D4);

if (!bPowerState)
{ //Power off the radio
hNdis = ::CreateFile(DD_NDIS_DEVICE_NAME, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL);
if (INVALID_HANDLE_VALUE != hNdis)
{
bRes = ::DeviceIoControl(hNdis, IOCTL_NDIS_UNBIND_ADAPTER, g_pszAdapterName, g_dwAdapterNameLen, NULL, 0, &dwBytesOut, NULL);
if (bRes)
{
hDriver = ::CreateFile(g_pszDriverName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL);
if (INVALID_HANDLE_VALUE != hDriver)
{
bRes = ::DeviceIoControl(hDriver, IOCTL_POWER_SET, NULL, 0, &ps, sizeof(ps), &dwBytesOut, NULL);
}
else bRes = FALSE;
::CloseHandle(hDriver);

}
::CloseHandle(hNdis);
}
}
else
{ //Power on the radio
hDriver = ::CreateFile(g_pszDriverName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL);
if (INVALID_HANDLE_VALUE != hDriver)
{
bRes = ::DeviceIoControl(hDriver, IOCTL_POWER_SET, NULL, 0, &ps, sizeof(ps), &dwBytesOut, NULL);
if (bRes)
{
hNdis = ::CreateFile(DD_NDIS_DEVICE_NAME, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL);
if (INVALID_HANDLE_VALUE != hNdis)
{
bRes = ::DeviceIoControl(hNdis, IOCTL_NDIS_BIND_ADAPTER, g_pszAdapterName, g_dwAdapterNameLen, NULL, 0, &dwBytesOut, NULL);
::CloseHandle(hNdis);
}
else bRes = FALSE;
}
::CloseHandle(hDriver);
}
}
return (bRes);
}

///////////////////////////////////////////
//Internal functions
void GetAdapterName()
{
DWORD dwError = 0;
DWORD dwSize = 0;
::GetAdaptersInfo(NULL, &dwSize);
IP_ADAPTER_INFO* pInfo = (IP_ADAPTER_INFO*)new char[dwSize];
TCHAR pszAdapterName[MAX_ADAPTER_NAME_LENGTH+4];

if ((dwError = ::GetAdaptersInfo(pInfo, &dwSize)) == NO_ERROR)
{
IP_ADAPTER_INFO* pCurrentAdapter = pInfo;
while (pCurrentAdapter)
{
if (pCurrentAdapter->Type == MIB_IF_TYPE_ETHERNET)
{
MultiByteToWideChar( CP_ACP, 0, pCurrentAdapter->AdapterName, -1, pszAdapterName, sizeof(pszAdapterName) / sizeof(pszAdapterName[0]));
//We assume there is only one ethernet adapter per device
break;
}
pCurrentAdapter = pCurrentAdapter->Next;
}
}
//Add the terminating zeros required by the IOCTL_NDIS_BIND_ADAPTER and IOCTL_NDIS_UNBIND_ADAPTER ioctls
DWORD dwLen = _tcslen(pszAdapterName)*sizeof(TCHAR);
g_dwAdapterNameLen = (_tcslen(pszAdapterName)+3)*sizeof(TCHAR);
//Free previous use of the adapter name string
if (g_pszAdapterName != NULL) {
::LocalFree(g_pszAdapterName);
g_pszAdapterName = NULL;
}
g_pszAdapterName = (TCHAR*)::LocalAlloc(LPTR, g_dwAdapterNameLen);
memmove(g_pszAdapterName, pszAdapterName, dwLen);
delete [] pInfo;
}

5 Comments:

At 2:38 AM, Blogger Hiep Trinh said...

Hi Robert,

Thank you for posting the code. I tried but it does not work on my O2 Atom XDA. If wifi is turned off, the function GetAdapterName() will not return the name of the wireless adapter. It seems Windows loads the driver by reading registry entry in HKEY_LOCAL_MACHINE\Comm\...? Or there is some secret routine here? AFAIK, iPAQ provides an iPAQUtil.dll that provides functions to turn on/off wifi and it really works on iPAQs.

I dont know if there is a way to do this hardware-independently.

Cheers,
T.H

 
At 2:31 AM, Blogger Unknown said...

This comment has been removed by the author.

 
At 2:34 AM, Blogger Unknown said...

This comment has been removed by the author.

 
At 2:38 AM, Blogger Unknown said...

Sorry, I don't speak English :(

...So, a simpler way is using Power Management API:

#define NDIS_MINIPORT_DRIVER
#pragma warning(push, 3)
#include <windows.h>
#include <tchar.h>
#include <Ntddndis.h>
#include <ndis.h> // redacted!
#include <winioctl.h>
#include <Nuiouser.h>
#include <Windot11.h>
#pragma warning(pop)
#pragma warning(push, 4)
#include <string>
#include <vector>

typedef std::vector<std::wstring> StringArray;

bool EnumDevices(StringArray& devList);
std::wstring GetDeviceName(StringArray& devList);


// get power state
CEDEVICE_POWER_STATE GetState()
{
/* TCHAR DeviceName[] = _T("{98C5250D-C29A-4985-AE5F-AFE5367E5006}\\TNETW12511"); */
std::wstring DeviceName = PMCLASS_NDIS_MINIPORT;
StringArray devList;
EnumDevices(devList);
DeviceName += GetDeviceName(devList);
CEDEVICE_POWER_STATE state = PwrDeviceMaximum;
DWORD err = GetDevicePower(DeviceName, POWER_NAME | POWER_FORCE, &state);
return state;
}

// set power state
DWORD SetState(CEDEVICE_POWER_STATE state)
{
std::wstring DeviceName = PMCLASS_NDIS_MINIPORT;
StringArray devList;
EnumDevices(devList);
DeviceName += GetDeviceName(devList);
DWORD err = SetDevicePower(DeviceName.c_str(), POWER_NAME | POWER_FORCE, state);
//hPower = SetPowerRequirement(DeviceName, state, POWER_NAME | POWER_FORCE, NULL, 0);
if ( err != ERROR_SUCCESS )
err = GetLastError();
return err;
}


bool EnumDevices(StringArray& devList)
{
UCHAR QueryBuffer[ 1024 ] = { 0 };
bool IsOk = false;

NDISUIO_QUERY_BINDING* pQueryBinding = (NDISUIO_QUERY_BINDING*) QueryBuffer;

for (DWORD i = 0; ; i++)
{
pQueryBinding->BindingIndex = i;
DWORD dwBytesReturned = 0;

if ( DeviceIoControl(g_hDev,
IOCTL_NDISUIO_QUERY_BINDING,
(VOID*) QueryBuffer, sizeof(QueryBuffer),
(VOID*) QueryBuffer, sizeof(QueryBuffer),
&dwBytesReturned, NULL) )
{
// Get the device name in the list of bindings
WCHAR* devName = (WCHAR*) LocalAlloc(
LPTR, pQueryBinding->DeviceNameLength * sizeof(WCHAR) );

memcpy( devName,
(UCHAR*) pQueryBinding + pQueryBinding->DeviceNameOffset,
pQueryBinding->DeviceNameLength );
devList.push_back( devName );

delete devName;
memset(QueryBuffer, 0, sizeof(QueryBuffer));
}
else
{
if (GetLastError() == ERROR_NO_MORE_ITEMS)
IsOk = true;
break;
}
}

return IsOk;
}

std::wstring GetDeviceName(StringArray& devList)
{
std::wstring devName;
for (StringArray::const_iterator it = devList.begin();
it != devList.end(); ++it)
if (*it != _T("WWAN1") && *it != _T("RNDISFN1"))
{
devName = *it;
return devName;
}
return devName;
}


Also see "Program Files\Windows CE Tools\wce500\Windows Mobile 5.0 Smartphone SDK\Samples\CPP\Win32\Powermanager" sample from SDK.

 
At 12:31 PM, Anonymous Anonymous said...

Hello Robert, do you have any sample project for windows mobile?.

 

Post a Comment

<< Home