Radio Power
An in-depth view in to monitoring and controlling the power of your Windows Mobile device's wireless communications systems.
- Download Power Management API demo source - 16.7 KB
- Download Power Management API demo - 93.5 KB
- Download Telephony API demo source - 14.5 KB
- Download Telephony API demo - 29.4 KB
- Download Bluetooth Application Development API demo source - 12.9 KB
- Download Bluetooth Application Development API demo - 28.5 KB
- Download Wireless Device Power Management API demo source - 15.9 KB
- Download Wireless Device Power Management API demo - 31.7 KB
|
|
|
|
Contents
- Introduction
- Power Management API
- Telephony API
- Bluetooth Application Development API
- Wireless Device Power Management API (OSSVCS.dll)
- Conclusion
Introduction
In this article, we will explore methods for enabling and disabling the wireless communications radios in your Windows Mobile device. We will first investigate methods that work for single radio types, and end with an unsupported method for managing the state of all radios.
Method | Radio types supported |
Power Management API | WiFi |
Telephony API | Cellular (WWAN) |
BTH Util API | Bluetooth |
OSSVCS | WiFi, WWAN, Bluetooth |
As a side note, the attached demo applications use boost. I considered writing them using with only what comes with the Windows Mobile 6 SDK, but the boost libraries do such an incredible job of making the code more readable, exception-safe, and usable, that I decided to use them. If you're not using boost (and you really should!), the power-management concepts presented in this article will still apply.
Power Management API
The Power Management API allows our applications to monitor and alter the power state of any power managed device (i.e., a device whose driver advertises power management capabilities). Generally, this is limited to the WiFi 802.11 adapter which uses a power-managed NDIS miniport driver.
Changing the power state
The Power Management API provides us with two deceptively simple calls to determine the current state of the radio and to change the state: GetDevicePower()
and SetDevicePower()
. I call them "deceptively simple" because while two of their three parameters are straightforward, the first, pvDevice
, is not.
In order to set or get the power state of a device using the Power Management API, we must know its power class and name. The power class GUID for an NDIS device is, by default, {98C5250D-C29A-4985-AE5F-AFE5367E5006}. The name of the WiFi radio is somewhat less simple to get at.
Locating the WiFi radio
A number of APIs will yield the names of the attached network devices. We will use the IpHelperAPI
because unlike IOCTL_NDISUIO_QUERY_BINDING
, it works regardless of whether the radio is currently on or off.
// Get the names of all NDIS adapters iin the device. We use the
// IPHelperAPI functions because IOCTL_NDISUIO_QUERY_BINDING won't
// work for adapters that aren't currently powered on.
DWORD buffer_size = 0;
if( ERROR_BUFFER_OVERFLOW == GetAdaptersInfo( NULL, &buffer_size ) )
{
std::vector< BYTE > adapter_buffer( buffer_size );
IP_ADAPTER_INFO* info =
reinterpret_cast< IP_ADAPTER_INFO* >( &adapter_buffer.front() );
DWORD res = GetAdaptersInfo( info, &buffer_size );
if( ERROR_SUCCESS == res )
{
// Which IP_ADAPTER_INFO is the WiFi adapter?
}
}
Now we have a list of all the network adapters, but we need a method of determining which is a WiFi adapter. One good method is to ask it a question that only an 802.11 adapter could answer. For this example, we will query the current SSID using OID_802_11_SSID
.
There are four steps to querying an NDIS OID:
- Open a handle to
NDISUIO_DEVICE_NAME
. - Build the
IOCTL_NDISUIO_QUERY_OID_VALUE
query structure. - Issue the query through
DeviceIoControl()
. - Retrieve the results from the query buffer.
First, we use CreateFile()
to open a handle to the NDIS protocol driver.
// Open a handle to the NDIS device. boost::shared_ptr<> provides us a
// method of ensuring our handle will be closed even if an exception is
// thrown.
boost::shared_ptr< void > ndis( ::CreateFile( NDISUIO_DEVICE_NAME,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
INVALID_HANDLE_VALUE ),
&::CloseHandle );
if( INVALID_HANDLE_VALUE != ndis.get() )
{
// use the NDIS protocol driver handle wisely
}
Next, we prepare an NDIS_802_11_SSID
buffer to receive the SSID. We will provide each adapter name given to us using GetAdaptersInfo()
. If the WiFi adapter is already on, QueryOid()
will return ERROR_SUCCESS
. If not, it will fail and return ERROR_GEN_FAILURE
. An ERROR_INVALID_PARAMETER
error indicates we supplied the name of a non-802.11 adapter.
/// get the SSID of the specified NDIS adapter
DWORD GetSSID( HANDLE ndis,
const std::wstring& adapter_name,
std::wstring* ssid = NULL )
{
std::vector< BYTE > ssid_buffer( sizeof( NDIS_802_11_SSID ) );
NDIS_802_11_SSID* ssid_int =
reinterpret_cast< NDIS_802_11_SSID* >( &ssid_buffer.front() );
ssid_int->SsidLength = NDIS_802_11_LENGTH_SSID - 1;
DWORD res = QueryOid( ndis,
adapter_name.c_str(),
OID_802_11_SSID,
&ssid_buffer );
if( ERROR_SUCCESS == res && NULL != ssid )
*ssid = reinterpret_cast< wchar_t* >( ssid_int->Ssid );
return res;
}
Third, we create an NDISUIO_QUERY_OID
buffer and supply it to DeviceIoControl
. The returned buffer should contain the SSID.
/// Query an NDIS OID. The output buffer must be pre-sized to be large enough
/// for the expected result of the query.
DWORD QueryOid( HANDLE ndis,
const wchar_t* device_name,
ULONG oid,
std::vector< BYTE >* buffer )
{
DWORD bytes_returned = 0;
std::vector< BYTE > query_buffer( sizeof( NDISUIO_QUERY_OID ) + buffer->size() );
NDISUIO_QUERY_OID* query =
reinterpret_cast< NDISUIO_QUERY_OID* >( &query_buffer.front() );
query->Oid = oid;
query->ptcDeviceName = const_cast< wchar_t* >( device_name );
BOOL res = ::DeviceIoControl( ndis,
IOCTL_NDISUIO_QUERY_OID_VALUE,
reinterpret_cast< LPVOID >( &query_buffer.front() ),
query_buffer.size(),
reinterpret_cast< LPVOID >( &query_buffer.front() ),
query_buffer.size(),
&bytes_returned,
NULL );
if( res )
{
// extract the result from the query buffer
std::copy( query->Data, query_buffer.end(), buffer->begin() );
return ERROR_SUCCESS;
}
return GetLastError();
}
Putting it all together, we can now determine which IP_ADAPTER_INFO
structure represents our WiFi adapter:
// Check each adapter to see if it responds to the standard
// OID_802_11_SSID query. If it does, then it is an 802.11
// adapter.
for( IP_ADAPTER_INFO* i = info; NULL != i; i = i->Next )
{
// convert the adapter name from narrow to wide-character
std::wstringstream adapter_name;
adapter_name << i->AdapterName;
// If the WiFi adapter is already on, QueryOid() will return
// ERROR_SUCCCESS. If not, it will fail and return
// ERROR_GEN_FAILURE. If it weren't an 802.11 adapter, it
// would return ERROR_INVALID_PARAMETER. Therefore, we will
// consider ERROR_GEN_FAILURE a success.
DWORD res = GetSSID( ndis.get(), adapter_name.str() );
if( ERROR_SUCCESS == res || ERROR_GEN_FAILURE == res )
{
adapter_name.str().swap( *name );
return true;
}
}
Enabling and disabling the radio
To change the power state of the NDIS WLAN radio, we combine the NDIS power management GUID with the name of the WLAN radio and pass it to SetDevicePower()
. Power state D4
will turn the radio off, and D0
will turn it on.
void DoChangePowerState( const CEDEVICE_POWER_STATE& power_state )
{
std::wstring radio_name;
GetRadioName( &radio_name );
if( radio_name_.length() > 0 )
{
std::wstringstream power_command;
power_command << PMCLASS_NDIS_MINIPORT << L\\" << radio_name.c_str();
::SetDevicePower( const_cast< wchar_t* >( power_command.str().c_str() ),
POWER_NAME,
power_state );
}
}
void Enable()
{
DoChangePowerState( D0 );
}
void Disable()
{
DoChangePowerState( D4 );
}
Monitoring changes to the power state
In addition to changing the power state of the WLAN radio, the Power Manager API can be used to alert us when the power state changes. There are five steps to this process:
- Open a handle to the NDIS protocol driver.
- Create a system message queue.
- Request NDIS power notifications with
IOCTL_NDISUIO_REQUEST_NOTIFICATION
. - Wait for NDIS power notification messages to arrive.
- Read the power notification message from the queue to determine if the radio is off or on.
First, we open the NDIS protocol driver handle exactly as we did earlier when retrieving the SSID:
// Open a handle to the NDIS device. boost::shared_ptr<> provides us a
// method of ensuring our handle will be closed even if an exception is
// thrown.
boost::shared_ptr< void > ndis( ::CreateFile( NDISUIO_DEVICE_NAME,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
INVALID_HANDLE_VALUE ),
&::CloseHandle );
if( INVALID_HANDLE_VALUE != ndis.get() )
{
// use the NDIS protocol driver handle wisely
}
Next, we create a read-only message queue to listen for NDIS notifications:
MSGQUEUEOPTIONS options = { 0 };
options.dwSize = sizeof( MSGQUEUEOPTIONS );
options.cbMaxMessage = sizeof( NDISUIO_DEVICE_NOTIFICATION );
options.bReadAccess = TRUE;
options.dwFlags = MSGQUEUE_NOPRECOMMIT;
boost::shared_ptr< void > radio_power_queue( ::CreateMsgQueue( NULL, &options ),
&::CloseMsgQueue );
if( NULL != radio_power_queue.get() )
{
// The message queue is open and ready for reading
}
We use IOCTL_NDISUIO_REQUEST_NOTIFICATION
to request power state change notifications in our NDIS message queue. A struct
provides a handy exception-safe method of ensuring we properly cancel our notification request regardless of what may happen in the future.
/// encapsulates the registration and de-registration for ndis power-state
/// notifications
struct NdisRequestNotifications : private boost::noncopyable
{
NdisRequestNotifications( HANDLE ndis,
HANDLE queue,
DWORD dwNotificationTypes )
: ndis_( ndis )
{
NDISUIO_REQUEST_NOTIFICATION radio_power_notifications = { 0 };
radio_power_notifications.hMsgQueue = queue;
radio_power_notifications.dwNotificationTypes = dwNotificationTypes;
::DeviceIoControl( ndis_,
IOCTL_NDISUIO_REQUEST_NOTIFICATION,
&radio_power_notifications,
sizeof( NDISUIO_REQUEST_NOTIFICATION ),
NULL,
0,
NULL,
NULL );
};
~NdisRequestNotifications()
{
// stop receiving NDIS power notifications
::DeviceIoControl( ndis_,
IOCTL_NDISUIO_CANCEL_NOTIFICATION,
NULL,
NULL,
NULL,
0,
NULL,
NULL );
};
private:
/// handle to the NDIS instance we're listening for power notifications on
HANDLE ndis_;
}; // struct NdisRequestNotifications
// use the struct to request notifications whenever the device is turned on or off.
NdisRequestNotifications notifications( ndis.get(),
radio_power_queue.get(),
NDISUIO_NOTIFICATION_DEVICE_POWER_DOWN |
NDISUIO_NOTIFICATION_DEVICE_POWER_UP );
Now, we must wait for the message queue to alert us if a message is available. A second event handle can be used to cancel the thread read-loop at any time.
/// Event signaled when we should stop listening for power state change messages
HANDLE stop_notification_;
// wait for either a change in radio state or for the user to ask us
// to stop listening.
HANDLE wait_objects[] = { radio_power_queue.get(), stop_notification_ };
size_t object_count = _countof( wait_objects );
while( ::WaitForMultipleObjects( object_count,
wait_objects,
FALSE,
INFINITE ) == WAIT_OBJECT_0 )
{
// A message is available on the queue!
}
Finally, when we have received a message, we use the ReadMsgQueue()
function to extract the message from the queue. The power state of the radio will be stored in the dwNotificationType
parameter of NDISUIO_DEVICE_NOTIFICATION
.
// The radio has changed state. Read the change from the queue.
NDISUIO_DEVICE_NOTIFICATION notification = { 0 };
DWORD bytes_read = 0;
DWORD notification_flags = 0;
if( ::ReadMsgQueue( radio_power_queue.get(),
¬ification,
sizeof( NDISUIO_DEVICE_NOTIFICATION ),
&bytes_read,
0,
¬ification_flags ) &&
bytes_read > 0 )
{
// Is the radio enabled?
bool enabled =
( notification.dwNotificationType == NDISUIO_NOTIFICATION_DEVICE_POWER_UP );
}
Telephony API
The Microsoft Telephony Service Provider API (TAPI) will allow us to manage the power state of the Wireless Wide-Area Network (WWAN) radio. Because the functions we will use for this purpose are part of the Extended TAPI interface, which deals entirely with cellular radios, this method will not work for any other radio type.
Debugging Note
If the Microsoft Visual Studio 2008 debugger stops code execution before the TAPI cleanup functions have run, you may get an error like this:
It is irritating, but does not seem to have a negative impact on the Visual Studio session or on the mobile device.
Changing the power state
First, we must initialize the TAPI subsystem with lineInitializeEx()
. This not only gives us a handle object, but also tells us the number of TAPI devices in the system and the event HANDLE
TAPI signals when a message is available. In this example, we wrap the TAPI HLINEAPP
handle in a boost::shared_ptr<>
to manage the lifetime of the object.
/// Manages the lifetime of the TAPI HLINEAPP
class TapiLineApp
{
public:
TapiLineApp()
: line_app_( TapiLineApp::Create( &device_count_,
&event_,
&error_code_ ),
&::lineShutdown )
{
};
/// Get the error code returned by lineInitializeEx()
DWORD GetErrorCode() const { return error_code_; };
/// Get the number of TAPI devices in the system
DWORD GetDeviceCount() const { return device_count_; };
/// Get the TAPI message event handle
HANDLE GetMessageEvent() const { return event_; };
operator HLINEAPP() const { return ( HLINEAPP )line_app_.get(); };
private:
///////////////////////////////////////////////////////////////////////
// Method private static TapiLineApp::Create
/// @brief Initialize the TAPI subsystem
///
/// @param DWORD * device_count - [out] number of TAPI devices
/// @param HANDLE * event_ - [out] event signaled when TAPI has a message.
/// @param DWORD * error_code - [out] 0 on success. TAPI error on failure.
/// @return HLINEAPP - TAPI handle
///////////////////////////////////////////////////////////////////////
static HLINEAPP Create( DWORD* device_count,
HANDLE* event_,
DWORD* error_code )
{
DWORD api_ver = TAPI_CURRENT_VERSION;
LINEINITIALIZEEXPARAMS init_params = { 0 };
init_params.dwTotalSize = sizeof( LINEINITIALIZEEXPARAMS );
init_params.dwOptions = LINEINITIALIZEEXOPTION_USEEVENT;
HLINEAPP line_app = NULL;
*error_code = ::lineInitializeEx( &line_app,
NULL,
NULL,
NULL,
device_count,
&api_ver,
&init_params );
if( *error_code == 0 )
{
*event_ = init_params.Handles.hEvent;
return line_app;
}
return NULL;
};
private:
// because we are using a static initializer function, the order of these
// members is important. Don't change it!
/// number of TAPI devices in the system
DWORD device_count_;
/// error code returned by lineInitializeEx()
DWORD error_code_;
/// TAPI message event handle
HANDLE event_;
/// TAPI line app instance
boost::shared_ptr< const HLINEAPP__ > line_app_;
}; // class TapiLineApp
Next, we use lineOpen()
to open a handle to the WWAN radio via the Cellular TAPI Service Provider (TSP). As in the previous example, we use a boost::shared_ptr<>
to manage the lifetime of the HLINE
handle.
/// Manages the lifetime of the TAPI HLINE
class TapiLine
{
public:
TapiLine( const TapiLineApp& line_app,
DWORD line_id,
DWORD version,
DWORD privilege ) :
line_app_( line_app ),
line_( TapiLine::Create( line_app,
line_id,
version,
privilege,
&error_code_ ),
&::lineClose )
{
};
/// Get the error code returned by lineOpen()
DWORD GetErrorCode() const { return error_code_; };
operator HLINE() const { return ( HLINE )line_.get(); };
private:
///////////////////////////////////////////////////////////////////////
// Method private static TapiLine::Create
/// @brief Open a TAPI line
///
/// @param HLINEAPP line_app - TAPI instance handle
/// @param DWORD line_id - line to open
/// @param DWORD version - TAPI version to use
/// @param DWORD privilege - TAPI LINECALLPRIVILEGE_*
/// @param DWORD * error_code - [out] error code returned by lineOpen.
/// @return HLINE - TAPI line handle
///////////////////////////////////////////////////////////////////////
static HLINE Create( HLINEAPP line_app,
DWORD line_id,
DWORD version,
DWORD privilege,
DWORD* error_code )
{
HLINE line = NULL;
*error_code = ::lineOpen( line_app,
line_id,
&line,
version,
0,
NULL,
privilege,
NULL,
NULL );
if( *error_code == 0 )
{
return line;
}
return NULL;
};
private:
// because we are using a static initializer function, the order of these
// members is important. Don't change it!
/// error returned by lineOpen()
DWORD error_code_;
/// handle to the TAPI line app
TapiLineApp line_app_;
/// handle to the TAPI line
boost::shared_ptr< const HLINE__ > line_;
}; // class TapiLine
Now, we are able to use lineGetEquipmentState()
to easily get the current state of the WWAN radio:
/// Open TAPI line handle
TapiLine line_;
/// Is the WWAN radio enabled?
bool IsRadioEnabled() const
{
DWORD equip_state = 0, radio_state = 0;
if( ::lineGetEquipmentState( line_, &equip_state, &radio_state ) == 0 )
{
return ( LINEEQUIPSTATE_MINIMUM != equip_state );
}
return false;
}
lineSetEquipmentState()
can be used to enable the WWAN radio:
/// Open TAPI line handle
TapiLine line_;
/// enable the WWAN radio
void Enable() const
{
if( ::lineSetEquipmentState( line_, LINEEQUIPSTATE_FULL ) == 0 )
{
::lineRegister( line_, LINEREGMODE_AUTOMATIC, NULL, LINEOPFORMAT_NONE );
}
}
We can even put it in to Flight Mode:
/// Open TAPI line handle
TapiLine line_;
/// disable the WWAN radio
void Disable() const
{
::lineSetEquipmentState( line_, LINEEQUIPSTATE_MINIMUM );
}
Monitoring changes to the power state
Listening for changes in the WWAN radio state is easy. We initialize TAPI just as we did earlier. Then, we use lineGetMessage()
to read messages from the TAPI message queue. When the Extended TAPI LINE_DEVSPECIFIC
message arrives with a LINE_EQUIPSTATECHANGE
value for dwParam2
, then we know the power state has changed. The new power state is stored in dwParam2
of the LINEMESSAGE
.
/// event signaled when we should stop listening for TAPI messages
HANDLE stop_notification_;
/// TAPI instance
TapiLineApp line_app_;
void MonitorRadioState()
{
HANDLE wait_objects[] = { line_app_.GetMessageEvent(),
stop_notification_ };
size_t object_count = _countof( wait_objects );
// wait for either a message from TAPI or for the user to ask us to stop
// listening.
while( ::WaitForMultipleObjects( object_count,
wait_objects,
FALSE,
INFINITE ) == WAIT_OBJECT_0 )
{
// A TAPI message has arrived, Read the message from the queue.
LINEMESSAGE message = { 0 };
if( ::lineGetMessage( line_app_, &message, 0 ) == 0 )
{
switch( message.dwMessageID )
{
case LINE_DEVSPECIFIC:
if( LINE_EQUIPSTATECHANGE == message.dwParam1 )
{
// Is the radio enabled?
bool enabled = ( message.dwParam2 != LINEEQUIPSTATE_MINIMUM );
}
break;
}
}
}
}
Bluetooth Application Development API
Of all the methods we will examine, the Bluetooth Application Development API provides us the simplest access to the power state of a radio.
Changing the power state
Only two commands are needed to check and change the power state of the Bluetooth radio: BthGetMode()
and BthSetMode()
. They don't require any supporting handle or structures so, as you will see, they are extremely easy to use.
Is the Bluetooth radio enabled?
bool IsRadioEnabled() const
{
DWORD mode = 0;
if( ::BthGetMode( &mode ) == ERROR_SUCCESS )
return ( BTH_POWER_OFF != mode );
return false;
}
Enable the Bluetooth radio:
void Enable() const { ::BthSetMode( BTH_DISCOVERABLE ); }
Disable the Bluetooth radio:
void Disable() const { ::BthSetMode( BTH_POWER_OFF ); }
Monitoring changes to the power state
The technique we will use to monitor the power state of the Bluetooth radio is very similar to the method we used earlier for monitoring the WiFi radio state.
- Use
CreateMsgQueue()
to open a message queue. - Use
RequestBluetoothNotifications()
to request a notification be sent to our queue when the Bluetooth stack comes up or goes down. - Wait for a new message on the queue or for the user to request a stop.
- When a new event is signaled, read the
BTEVENT
message. The power state is stored in thedwEventId
parameter.
/// event signaled when we should stop listening for Bluetooth messages
HANDLE stop_notification_;
/// an exception safe handle encapsulation
typedef boost::shared_ptr< void > SafeHandle;
void MonitorRadioState()
{
// create a message queue to listen for Bluetooth power notifications
MSGQUEUEOPTIONS options = { 0 };
options.dwSize = sizeof( MSGQUEUEOPTIONS );
options.cbMaxMessage = sizeof( BTEVENT );
options.bReadAccess = TRUE;
options.dwFlags = MSGQUEUE_NOPRECOMMIT;
SafeHandle radio_power_queue( ::CreateMsgQueue( NULL, &options ),
&::CloseMsgQueue );
if( NULL != radio_power_queue.get() )
{
// request Bluetooth stack notifications
SafeHandle notifications(
::RequestBluetoothNotifications( BTE_CLASS_STACK, radio_power_queue.get() ),
&::StopBluetoothNotifications );
// wait for either a change in radio state or for the user to ask us
// to stop listening.
HANDLE wait_objects[] = { radio_power_queue.get(),
stop_notification_ };
size_t object_count = _countof( wait_objects );
while( ::WaitForMultipleObjects( object_count,
wait_objects,
FALSE,
INFINITE ) == WAIT_OBJECT_0 )
{
// The radio has changed state. Read the change from the queue.
BTEVENT notification = { 0 };
DWORD bytes_read = 0;
DWORD notification_flags = 0;
if( ::ReadMsgQueue( radio_power_queue.get(),
¬ification,
sizeof( BTEVENT ),
&bytes_read,
0,
¬ification_flags ) &&
bytes_read > 0 )
{
// Is the radio enabled?
bool enabled = ( notification.dwEventId == BTE_STACK_UP );
}
}
}
}
Wireless Device Power Management API (OSSVCS.dll)
The Wireless Power Management APIs provide functions that manage the power state of the wireless radios available on Windows Mobile devices. This includes the Bluetooth, Phone, and WiFi radios. It is commonly used in applications like the Wireless Manager (above image) that are created by the device OEM or Microsoft.
Note
Unfortunately, the Wireless Device Power Management API is not published by Microsoft in their public Windows Mobile SDK; it is part of the Platform Builder. If you don't have access to the Platform Builder, you will need to modify the demo project to dynamically link to ossvcs.dll. Its usage is sufficiently common that the wrlspwr.h interfaces can be easily found through a quick Internet search.
Changing the power state
The GetWirelessDevices()
function provides us with a linked list of RDD
structures defining the current state of each of the NDIS devices that support the Windows Embedded CE Power Management features (i.e., the Bluetooth, Cellcore, and WiFi devices). As this structure must be freed with a call to FreeDeviceList()
, we can use the boost::shared_ptr<>
to manage its lifetime.
/// encapsulate the lifetime management of the RDD structure.
class OssvcsRadioList
{
public:
typedef detail::OssvcsListIterator const_iterator;
OssvcsRadioList()
: radio_list_( OssvcsRadioList::Create(), &::FreeDeviceList )
{
};
explicit OssvcsRadioList( RDD* list )
: radio_list_( list, &::FreeDeviceList )
{
};
const_iterator begin() const
{
return const_iterator( radio_list_.get() );
};
const_iterator end() const
{
return const_iterator();
};
private:
static RDD* Create( DWORD flags = 0, HRESULT* error_code = NULL )
{
RDD* radio_list = NULL;
HRESULT hr = ::GetWirelessDevices( &radio_list, flags );
if( NULL != error_code )
*error_code = hr;
return radio_list;
};
boost::shared_ptr< RDD > radio_list_;
}; // class OssvcsRadioList
The RDD
linked-list structure can be easily adapted to work with standard library functions by using boost::iterator_adaptor<>
.
/// Iterate over nodes in the RDD linked-list
class OssvcsListIterator
: public boost::iterator_facade< OssvcsListIterator,
const RDD*,
boost::forward_traversal_tag,
const RDD* >
{
public:
OssvcsListIterator() : node_( NULL ) {};
explicit OssvcsListIterator( const RDD* p ) : node_( p ) {};
private:
friend class boost::iterator_core_access;
void increment() { node_ = node_->pNext; };
bool equal( OssvcsListIterator const& other ) const { return other.node_ == node_; };
const RDD* dereference() const { return node_; };
const RDD* node_;
}; // class OssvcsListIterator
Finally, the ChangeRadioState()
method can be used to turn a particular radio on or off.
/// encapsulate power management functions
namespace OssvcsRadioPower {
/// Turn the specified radio device on
void Enable( const RDD* device )
{
::ChangeRadioState( const_cast< RDD* >( device ), 1, POWER_POST_SAVE );
};
/// Turn the specified radio device off
void Disable( const RDD* device )
{
::ChangeRadioState( const_cast< RDD* >( device ), 0, POWER_POST_SAVE );
};
/// Is the specified radio device on?
bool IsEnabled( const RDD* device )
{
return device->dwState != 0;
};
}; // namespace OssvcsRadioPower
Now we're able to implement fantastic 1-liners using standard library functions like std::for_each()
:
/// list of wireless devices
OssvcsRadioList radio_list_;
// Enable all radios
std::for_each( radio_list_.begin(), radio_list_.end(), OssvcsRadioPower::Enable );
Monitoring changes to the power state
The OSSVCS API does include a provision for alerting a program of changes to the wireless radios: GetAndMonitorWirelessDevices()
. Unfortunately, it is quite broken.
- The monitor thread quits after 30 seconds even if no radio changes state.
- Regardless of the flags used, the list of devices returned by
GetAndMonitorWirelessDevices()
doesn't include the 802.11 adapter. - Any process can signal the
WRLS_TERMINATE_EVENT
and cancel the monitor thread. - Any process' call to
GetWirelessDevices()
will cancel the monitor thread. - The callback is executed immediately after the
GetAndMonitorWirelessDevices()
call even if no device has yet changed state. - The monitor thread exits when a radio changes state, but does not activate the callback first.
- Turning the Bluetooth or WWAN radios on or off does not trigger the callback; only the WLAN adapter can trigger it.
Most of these issues could be worked around, but issue 7 means that even with the workarounds, the API is only useful for monitoring the state of 802.11 WLAN devices, and we have simpler methods for doing that. If I'm mistaken in any of the above points or you have managed to use this method successfully, please add a comment describing your method! I would love to see it.
The solution is to use the same method as the Wireless Manager uses: the State and Notification Broker.
State and Notification Broker
The Windows Mobile State and Notification Broker stores status information in specific Registry locations defined in snapi.h (mostly in [HKLM]\System\State). This allows us to use standard Registry queries to determine the current state of these devices, or use the Registry notification functions to be alerted of any change in state. Unfortunately, it cannot be used to modify the state of the radios, which is why we combine it with OSSVCS.
The RegistryNotifyCallback()
function requires us to define a callback function that will be alerted when a Registry value changes. This callback function must conform to the REGISTRYNOTIFYCALLBACK
definition. As with most Microsoft API functions, this callback allows us to pass a DWORD
parameter of our choosing. For convenience, our example will pass a this
pointer.
/// callback invoked by the notification broker when a registry value is changed.
/*static*/ void RadioNotification::Callback( HREGNOTIFY hNotify,
DWORD dwUserData,
const PBYTE /*pData*/,
const UINT /*cbData*/ )
{
RadioNotification* parent =
reinterpret_cast< RadioNotification* > ( dwUserData );
// The state of a radio has changed! Do something interesting.
// parent->DoSomethingInteresting();
}
A small helper-function allows us to simplify linking a Registry value to our callback function.
/// Link a registry value to our callback function.
HREGNOTIFY RadioNotification::LinkToCallback( DWORD bitmask,
HKEY root,
LPCTSTR path,
LPCTSTR value ) const
{
NOTIFICATIONCONDITION condition = { REG_CT_ANYCHANGE, bitmask, 0 };
HREGNOTIFY notification = 0;
::RegistryNotifyCallback( root,
path,
value,
&RadioNotification::Callback,
( DWORD )this,
&condition,
¬ification );
return notification;
}
Now, we can provide a method our application can use to start listening for changes in the state of the WiFi, Bluetooth, or Cellular radios. As always, the boost::shared_ptr<>
comes in handy for safely managing the lifetime of our handles.
/// manage the lifetime of HREGNOTIFY handles
typedef boost::shared_ptr< HREGNOTIFY__ > RegNotify;
/// handle to the WWAN phone power notification
RegNotify phone_notification_;
/// handle to the WLAN WiFi power notification
RegNotify wifi_notification_;
/// handle to the bluetooth power notification
RegNotify bluetooth_notification_;
/// start listening for changes to the radios' state
void RadioNotification::Start()
{
// get changes to the bluetooth radio power state
bluetooth_notification_.reset(
CreateCallback( SN_BLUETOOTHSTATEPOWERON_BITMASK,
SN_BLUETOOTHSTATEPOWERON_ROOT,
SN_BLUETOOTHSTATEPOWERON_PATH,
SN_BLUETOOTHSTATEPOWERON_VALUE ),
&::RegistryCloseNotification );
// get changes to the wifi radio power state
wifi_notification_.reset( CreateCallback( SN_WIFISTATEPOWERON_BITMASK,
SN_WIFISTATEPOWERON_ROOT,
SN_WIFISTATEPOWERON_PATH,
SN_WIFISTATEPOWERON_VALUE ),
&::RegistryCloseNotification );
// get changes to the phone power state
phone_notification_.reset( CreateCallback( SN_PHONERADIOOFF_BITMASK,
SN_PHONERADIOOFF_ROOT,
SN_PHONERADIOOFF_PATH,
SN_PHONERADIOOFF_VALUE ),
&::RegistryCloseNotification );
}
Finally, we define a method to stop listening for changes in the radio state. This method need do nothing more than close the Registry notification handles.
/// stop listening for changes to the radios' state
void RadioNotification::Stop()
{
bluetooth_notification_.reset();
wifi_notification_.reset();
phone_notification_.reset();
}
Conclusion
We've examined every useful method I could think of to manage and monitor the power state of the wireless radios in a Windows Mobile device. If you use a different method or have a modification to one of the methods I described above, please describe it in a comment and I'll try to add it to the article.