Click here to Skip to main content
Click here to Skip to main content

Radio Power

, 19 Aug 2010
Rate this:
Please Sign up or sign in to vote.
An in-depth view in to monitoring and controlling the power of your Windows Mobile device's wireless communications systems.

PowerManagement Demo

TAPI Demo

Bluetooth Demo

OSSVCS Demo

Contents

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

PowerManagement Demo

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:

  1. Open a handle to NDISUIO_DEVICE_NAME.
  2. Build the IOCTL_NDISUIO_QUERY_OID_VALUE query structure.
  3. Issue the query through DeviceIoControl().
  4. 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:

  1. Open a handle to the NDIS protocol driver.
  2. Create a system message queue.
  3. Request NDIS power notifications with IOCTL_NDISUIO_REQUEST_NOTIFICATION.
  4. Wait for NDIS power notification messages to arrive.
  5. 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

TAPI Demo

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:

VS2008 Error 0x800400c3

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

Bluetooth Demo

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.

  1. Use CreateMsgQueue() to open a message queue.
  2. Use RequestBluetoothNotifications() to request a notification be sent to our queue when the Bluetooth stack comes up or goes down.
  3. Wait for a new message on the queue or for the user to request a stop.
  4. When a new event is signaled, read the BTEVENT message. The power state is stored in the dwEventId 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)

OSSVCS Demo

Wireless Manager

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.

  1. The monitor thread quits after 30 seconds even if no radio changes state.
  2. Regardless of the flags used, the list of devices returned by GetAndMonitorWirelessDevices() doesn't include the 802.11 adapter.
  3. Any process can signal the WRLS_TERMINATE_EVENT and cancel the monitor thread.
  4. Any process' call to GetWirelessDevices() will cancel the monitor thread.
  5. The callback is executed immediately after the GetAndMonitorWirelessDevices() call even if no device has yet changed state.
  6. The monitor thread exits when a radio changes state, but does not activate the callback first.
  7. 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Paul Heil
Software Developer (Senior) An engineering firm in Cedar Rapids, Iowa
United States United States
I'm also on the MSDN forums
http://social.msdn.microsoft.com/profile/paulh79

Comments and Discussions

 
QuestionWindows Phone 8 Pinmemberewgoforth2-Oct-13 1:11 
AnswerRe: Windows Phone 8 PinmemberPaul Heil3-Oct-13 6:21 
GeneralMy vote of 5 PinmemberMember 997428316-Apr-13 21:41 
QuestionGetting "ERROR_INVALID_PARAMETER" while calling GetDevicePower() \ SetDevicePower() PinmemberMember 997428316-Apr-13 4:53 
AnswerRe: Getting "ERROR_INVALID_PARAMETER" while calling GetDevicePower() \ SetDevicePower() PinmemberPaul Heil16-Apr-13 5:40 
QuestionNeed Windows API to change the power state of Bluetooth on Windows 8 Pinmembersudhirpotnuru8-Apr-13 22:27 
QuestionRe Pinmemberektel16-Oct-12 22:09 
Questionsecond request for notification fails PinmemberGeoffrey Coram23-Apr-12 14:57 
AnswerRe: second request for notification fails PinmemberPaul Heil23-Apr-12 15:48 
GeneralRe: second request for notification fails PinmemberGeoffrey Coram24-Apr-12 15:32 
GeneralRe: second request for notification fails PinmemberPaul Heil24-Apr-12 16:46 
GeneralRe: second request for notification fails PinmemberGeoffrey Coram25-Apr-12 0:39 
GeneralRe: second request for notification fails PinmemberOlwal27-Nov-12 21:37 
QuestionIpHelperAPI and powered-off radio PinmemberGeoffrey Coram23-Apr-12 14:49 
AnswerRe: IpHelperAPI and powered-off radio PinmemberPaul Heil23-Apr-12 15:54 
GeneralRe: IpHelperAPI and powered-off radio PinmemberGeoffrey Coram24-Apr-12 15:47 
QuestionNotification Icon Pinmemberg_s_p_20061-Feb-12 1:16 
QuestionC# Wrapper PinmemberTaras Tim Bredel30-Dec-11 3:58 
AnswerRe: C# Wrapper PinmemberPaul Heil3-Jan-12 3:39 
GeneralRe: C# Wrapper PinmemberTaras Tim Bredel3-Jan-12 4:24 
I've used this "instead"...
Though someone else could use it.
 

Notice, I only wanted to turn on phone (to ensure I can perform webservice calls).
If you also want to be able to turn off the radio, then the method (SwitchOff()) is already present in the PhoneRadio class, so you can add a threaded method in the PhoneController class using same schema.

 
---------------------------
C# wrapper P/Invoke
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
 
namespace Controllers
{
    public class PhoneRadio : IDisposable
    {
        private IntPtr hLine = IntPtr.Zero;
        private IntPtr hLineApp = IntPtr.Zero;
        private bool disposed = false;
 
        //used in lineInitializeEx function
        struct LINEINITIALIZEEXPARAMS
        {
            public int dwTotalSize;
            public int dwNeededSize;
            public int dwUsedSize;
            public int dwOptions;
            public IntPtr hEvent;
            public int dwCompletionKey;
        } ;
 
        private enum LINEINITIALIZEEXOPTION : int
        {
            LINEINITIALIZEEXOPTION_USEHIDDENWINDOW = 1,
            LINEINITIALIZEEXOPTION_USEEVENT = 2,
            LINEINITIALIZEEXOPTION_USECOMPLETIONPORT = 3
        }
 

        private enum LineErrReturn : uint
        {
            LINE_OK = 0x00000000,
            LINEERR_INVALAPPNAME = 0x80000015,
            LINEERR_OPERATIONFAILED = 0x80000048,
            LINEERR_INIFILECORRUPT = 0x8000000E,
            LINEERR_INVALPOINTER = 0x80000035,
            LINEERR_REINIT = 0x80000052,
            LINEERR_NOMEM = 0x80000044,
            LINEERR_INVALPARAM = 0x80000032
        }
 
        //Used in lineNegotiateAPIVersion function
        struct LINEEXTENSIONID
        {
            public IntPtr dwExtensionID0;
            public IntPtr dwExtensionID1;
            public IntPtr dwExtensionID2;
            public IntPtr dwExtensionID3;
        }
 
        //Used in lineSetEquipmentState function
        private enum LINEEQUIPSTATE : int
        {
            LINEEQUIPSTATE_MINIMUM = 1,
            LINEEQUIPSTATE_RXONLY = 2,
            LINEEQUIPSTATE_TXONLY = 3,
            LINEEQUIPSTATE_NOTXRX = 4,
            LINEEQUIPSTATE_FULL = 5
        }
 
        //Used in LineRegister function
        private enum LINEREGMODE : int
        {
            LINEREGMODE_AUTOMATIC = 1,
            LINEREGMODE_MANUAL = 2,
            LINEREGMODE_MANAUTO = 3
        }
 
        private enum LINEOPFORMAT : int
        {
            LINEOPFORMAT_NONE = 0,
            LINEOPFORMAT_ALPHASHORT = 1,
            LINEOPFORMAT_ALPHALONG = 2,
            LINEOPFORMAT_NUMERIC = 4
        }
 
        [DllImport("coredll.dll", SetLastError = true)]
        private static extern LineErrReturn lineInitializeEx(
            out IntPtr hLineApp, 
            IntPtr hAppHandle, 
            IntPtr lCallBack, 
            string FriendlyAppName,
            out System.UInt32 NumDevices,
            ref System.UInt32 APIVersion,
            ref LINEINITIALIZEEXPARAMS lineExInitParams);
 
        [DllImport("coredll.dll")]
        private static extern int lineNegotiateAPIVersion(
            IntPtr lphLineApp,
            int dwDeviceID,
            int dwAPILowVersion,
            int dwAPIHighVersion,
            out int lpdwAPIVersion,
            out LINEEXTENSIONID lpExtensionID);
 
        [DllImport("coredll.dll")]
        private static extern int lineOpen(
            IntPtr hLineApp,
            int dwDeviceID,
            out IntPtr hLine,
            int dwAPIVersion,
            int dwExtVersion,
            int dwCallbackInstance,
            int dwPrivileges,
            int dwMediaModes,
            IntPtr lpCallParams);
 
        [DllImport("cellcore.dll")]
        private static extern int lineSetEquipmentState(
            IntPtr hLine,
            int dwState);
 
        [DllImport("coredll.dll")]
        private static extern int lineClose(IntPtr hLine);
 
        [DllImport("coredll.dll")]
        private static extern int lineShutdown(IntPtr hLine);
 
        [DllImport("cellcore.dll")]
        private static extern int lineRegister(
            IntPtr hLine,
            int dwRegisterMode,
            String lpszOperator,
            int dwOperatorFormat);
 

        public PhoneRadio()
        {
            uint dwNumDev;
            uint dwApiVersion = 0x20000;
            LINEINITIALIZEEXPARAMS initParams = new LINEINITIALIZEEXPARAMS();
            
            initParams.hEvent = IntPtr.Zero;
            initParams.dwTotalSize = Marshal.SizeOf(initParams);
            initParams.dwNeededSize = initParams.dwTotalSize;
            initParams.dwUsedSize = initParams.dwTotalSize;
            initParams.hEvent = System.IntPtr.Zero;
            initParams.dwOptions = (int)LINEINITIALIZEEXOPTION.LINEINITIALIZEEXOPTION_USEEVENT;
            LineErrReturn Ret = lineInitializeEx(out hLineApp, IntPtr.Zero, IntPtr.Zero, "deltaProfile", out dwNumDev, ref dwApiVersion, ref initParams);
 
            int dwLineApiVersion;
            LINEEXTENSIONID dummy;
            int iRetLineNegotiateAPIVersion = lineNegotiateAPIVersion(hLineApp, 0, 0x10004, 0x20000, out dwLineApiVersion, out dummy);
            int iRetLineOpen = lineOpen(hLineApp, 0, out hLine, dwLineApiVersion, 0, 0, 4, 0x10, IntPtr.Zero);
        }
 
        ~PhoneRadio()
        {
            //This part of code will be called by the GC
            Dispose(false);
        }
 

        /// <summary>
        /// Will turn OFF the FlightMode profile.
        /// GSM signal will be immediately turned ON, while GPRS signal will take around 60 seconds
        /// </summary>
        public void SwitchOn()
        {
            int iRetLineSetEquipmentState = lineSetEquipmentState(hLine, (int)LINEEQUIPSTATE.LINEEQUIPSTATE_FULL);
            int iRetLineRegister = lineRegister(hLine, (int)LINEREGMODE.LINEREGMODE_AUTOMATIC, null, (int)LINEOPFORMAT.LINEOPFORMAT_NONE);
        }
 

        /// <summary>
        /// Will turn ON the FlightMode profile
        /// </summary>
        public void SwitchOff()
        {
            int iRetLineSetEquipmentState = lineSetEquipmentState(hLine, (int)LINEEQUIPSTATE.LINEEQUIPSTATE_NOTXRX);
            //int iRetLineRegister = lineRegister(hLine, (int)LINEREGMODE.LINEREGMODE_AUTOMATIC, null, (int)LINEOPFORMAT.LINEOPFORMAT_NONE);
        }
 

        /// <summary>
        /// Cleans up the resources used by this object.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            // Take off the Finalization queue 
            // to prevent finalization code for this object
            // from executing a second time.
            GC.SuppressFinalize(this);
        }
 
        // Dispose(bool disposing) executes in two distinct scenarios.
        // If disposing equals true, the method has been called directly
        // or indirectly by a user's code. Managed and unmanaged resources
        // can be disposed.
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer(destructor) and you should not reference 
        // other objects. Only unmanaged resources can be disposed.
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    //User-initiated dispose. Cleans up managed code.
                }
 
                lineClose(hLine);
 
                hLine = IntPtr.Zero;
                lineShutdown(hLineApp);
                hLineApp = IntPtr.Zero;
                disposed = true;
            }
        }
    }
}
 
 
---------------------------
And a thread handler...
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using Microsoft.WindowsMobile.Status;
using System.Threading;
 
namespace Controllers
{
    public class PhoneController
    {
        PhoneRadio pr = new PhoneRadio();
 
        public void PowerPhoneRadio(object state)
        {
            int counter = 0;
            bool stopper = false;
            try
            {
                if (SystemState.PhoneRadioOff)
                {
                    pr.SwitchOn();
 
                    while (!stopper && !isConnected())
                    {
                        counter++;
                        Thread.Sleep(1000);
 
                        if (counter > 60) // we have waited for a minute
                        {
                            // timeout on phone turn on
                            stopper = true;
                            raisePowerPhoneRadioPostBack(false);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                // Phone is dead, and we could not revive it
            }
            finally
            {
                pr.Dispose();
            }
            raisePowerPhoneRadioPostBack(true);
        }
 
        private bool isConnected()
        {
            return SystemState.CellularSystemConnectedEdge
                || SystemState.CellularSystemConnectedGprs
                || SystemState.CellularSystemConnectedHsdpa
                || SystemState.CellularSystemConnectedUmts;
        }
 
        public event PowerPhoneRadioEventHandler PowerPhoneRadioPostBack;
        private void raisePowerPhoneRadioPostBack(bool powered)
        {
            if (PowerPhoneRadioPostBack!= null)
            {
                PowerPhoneRadioPostBack(powered);
            }
        }
 
        private bool disposed = false;
        public void Dispose()
        {
            Dispose(true);
        }
 
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    //User-initiated dispose. Cleans up managed code.
                }
                if (pr != null)
                {
                    pr.Dispose();
                    pr = null;
                }
 
                disposed = true;
            }
        }
    }
    public delegate void PowerPhoneRadioEventHandler(bool powered);
}
 
-------------------------
From GUI...
        private void init()
        {
            PhoneController pc = new PhoneController();
            pc.PowerPhoneRadioPostBack += new PowerPhoneRadioEventHandler(OnPowerPhoneRadioPostBack);
        }
        private void checkPhoneRadio()
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(pr.PowerPhoneRadio));
        }
        private bool isPhoneRadioPowered;
        void OnPowerPhoneRadioPostBack(bool powered)
        {
            isPhoneRadioPowered = powered;
            this.Invoke(new EventHandler(PowerPhoneRadioResponse));
        }
        private void PowerPhoneRadioResponse(object sender, EventArgs Args)
        {
            // continue GUI
            if (isPhoneRadioPowered)
            {
                // phone is on and can be used
            }
            else
            {
                // we did not succeed to turn it on
            }
        }
The great thing about standards is that there are so many to chose amoungst.

GeneralRe: C# Wrapper PinmemberMIXXU24-Sep-13 3:50 
Questioni already try this code but got error [modified] PinmemberAka011-Dec-11 19:09 
AnswerRe: i already try this code but got error PinmemberPaul Heil2-Dec-11 4:04 
GeneralRe: i already try this code but got error PinmemberAka018-Dec-11 12:17 
AnswerRe: i already try this code but got error PinmemberPaul Heil26-Apr-12 4:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140827.1 | Last Updated 19 Aug 2010
Article Copyright 2010 by Paul Heil
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid