Power Management
Windows CE is typically used in battery-powered systems, which makes power management critical for the proper operation of the system. Applications are for the most part blissfully unaware of the power issues of a Windows CE device, but sometimes you might need to address these issues.When the user powers down a battery-powered Windows CE device, the power system isn't powered off the way a PC powers off. Instead, the system is suspended. When the user powers up the device, the device isn't rebooted like a PC—it resumes, returning to the same state it was in before it was suspended. As a result, an application running before the system was suspended is still running when the system resumes. In fact, the application won't know that it was suspended at all unless it explicitly requested to be notified when the system was suspended. From an application perspective, power management has three aspects: querying the power state, changing the power state, and occasionally preventing the power state from changing.
Querying the Power State
To query the current power state of the system, you can call
DWORD GetSystemPowerStatusEx2 (PSYSTEM_POWER_STATUS_EX2 pSystemPowerStatusEx2,
DWORD dwLen, BOOL fUpdate);
This function takes three parameters: a pointer to a SYSTEM_POWER_STATUS_EX2 structure, the length of that structure, and a Boolean value that tells the operating system if it should query the battery driver during the call to get the latest information or to return the cached battery information. The system queries the battery approximately every 5 seconds, so if this third parameter is FALSE, the data is still not too stale. The SYSTEM_POWER_STATUS_EX2 structure is defined as
typedef struct _SYSTEM_POWER_STATUS_EX2 {
BYTE ACLineStatus;
BYTE BatteryFlag;
BYTE BatteryLifePercent;
BYTE Reserved1;
DWORD BatteryLifeTime;
DWORD BatteryFullLifeTime;
BYTE Reserved2;
BYTE BackupBatteryFlag;
BYTE BackupBatteryLifePercent;
BYTE Reserved3;
DWORD BackupBatteryLifeTime;
DWORD BackupBatteryFullLifeTime;
WORD BatteryVoltage;
DWORD BatteryCurrent;
DWORD BatteryAverageCurrent;
DWORD BatteryAverageInterval;
DWORD BatterymAHourConsumed;
DWORD BatteryTemperature;
DWORD BackupBatteryVoltage;
BYTE BatteryChemistry;
} SYSTEM_POWER_STATUS_EX2;
Before I describe this rather large structure, I must warn you that the data returned in this structure is only as accurate as the system's battery driver. This same structure is passed to the battery driver to query its status. Windows CE doesn't validate the data returned by the battery driver. The data returned by this function depends on the battery driver and therefore varies across different systems. For example, many systems won't report an accurate value for the battery level when the system is on AC power; other systems will. Applications using GetSystemPowerStatusEx2 should program defensively and test on all systems that might run the application.
The first field, ACLineStatus, contains a flag indicating whether the system is connected to AC power. The possible values are AC_LINE_OFFLINE, indicating that the system isn't on AC power; AC_LINE_ONLINE, indicating that the system is on AC power; AC_LINE_BACKUP_POWER; and AC_LINE_UNKNOWN. The BatteryFlag field, which provides a gross indication of the current state of the battery, can have one of the following values:
BATTERY_FLAG_HIGH The battery is fully or close to fully charged.
BATTERY_FLAG_LOW The battery has little charge left.
BATTERY_FLAG_CRITICAL The battery charge is at a critical state.
BATTERY_FLAG_CHARGINGThe battery is currently being charged.
BATTERY_FLAG_NO_BATTERYThe system has no battery.
BATTERY_FLAG_UNKNOWNThe battery state is unknown.
The BatteryLifePercent field contains the estimated percentage of charge remaining in the battery. Either the value will be between 0 and 100 or it will be 255, indicating that the percentage is unknown. The BatteryLifeTime field contains the estimated number of seconds remaining before the battery is exhausted. If this value can't be estimated, the field contains BATTERY_LIFE_UNKNOWN. The BatteryFullLifeTime field contains the estimated life in seconds of the battery when it is fully charged. If this value can't be estimated, the field contains BATTERY_LIFE_UNKNOWN. Note that on many systems, these lifetime values are difficult if not impossible to accurately measure. Many OEMs simply fill in BATTERY_LIFE_UNKNOWN for both fields.The next four fields (not counting the reserved fields) replicate the fields previously described except that they contain values for the system's backup battery. Again, because many of these values are difficult to measure, many systems simply return an "unknown" value for these fields.The remaining fields describe the electrical state of the battery and backup battery. Because many systems lack the capacity to measure these values, these fields are simply filled with the default "unknown" values. The final field, BatteryChemistry, contains a flag indicating the type of battery in the system. The currently defined self-describing values are
BATTERY_CHEMISTRY_ALKALINE
BATTERY_CHEMISTRY_NICD
BATTERY_CHEMISTRY_NIMH
BATTERY_CHEMISTRY_LION
BATTERY_CHEMISTRY_LIPOLY
BATTERY_CHEMISTRY_UNKNOWN
Changing the Power State
Applications can change the power state of the system by using a series of methods. In newer systems based on Windows CE .NET, the preferred method is to use the Power Manager, discussed later in this chapter. However, there are plenty of systems based on earlier versions of Windows CE as well as systems that use Windows CE .NET but do not contain the Power Manager. For these systems, the following techniques are handy.
Powering Down
An application can suspend the system by calling the little-documented GwesPowerOffSystem function. This function has been available for many versions of Windows CE but has only recently been documented. In fact, most SDKs don't include the prototype for the function, so you might have to provide the prototype. The function is defined as
void GwesPowerOffSystem(void);
The use of GwesPowerOffSystem is simple: simply call, and the system suspends.To those who prefer to avoid little-documented functions, you can also power off the system by simulating the action of a user pressing the Off button. You can easily enable your application to suspend the system by using the keybd_event function, as in
keybd_event (VK_OFF, 0, KEYEVENTF_SILENT, 0);
keybd_event (VK_OFF, 0, KEYEVENTF_SILENT | KEYEVENTF_KEYUP, 0);
The two calls to keybd_event simulate the press and release of the power button, which has the virtual key code of VK_OFF. Executing the preceding two lines of code will suspend the system. Because the virtual key code has to be seen and acted on by GWES, the two functions probably will both return and a few more statements will be executed before the system actually suspends. If it is important that your program stop work after calling the keybd_event functions, add a call to Sleep to cause the application to pause for a number of milliseconds, allowing time for GWES to truly suspend the system.
Turning Off the Screen
On systems with color backlit displays, the main power drain on the system isn't the CPU—it's the backlight. In some situations, an application needs to run, but doesn't need the screen. An example of this might be a music player application when the user is listening to the music, not watching the screen. In these situations, the ability to turn off the backlight can significantly improve battery life.
Of course, any application that turns off the backlight needs to have a simple and user-friendly way of reenabling the screen when the user wants to look at the screen. Also, remember that users typically think the unit is off if the screen is black, so plan accordingly. For example, a user might attempt to power on the system when it is already running, and in doing so, accidentally turn off the device. Also, when the system powers down the display in this fashion, it also disables the touch screen. This means that you can't tell the user to tap the screen to turn it back on. Instead, you need to use some other event such as a set time, the completion of a task, or the user pressing a button. Finally, the method discussed here, useful on most systems based on Windows CE 3.0 and later, has been superseded by the method provided by the Power Manager that was introduced in Windows CE .NET 4.0. For newer systems, check to see whether the Power Manager is available, and control the screen through it. If that fails, the ExtEscape method might work.On Windows CE, the control of the display is exposed through the ExtEscape function, which is a back door to the display and printer device drivers. Windows CE display drivers support a number of device escape codes, which are documented in the Platform Builder. For our purposes, only two escape codes are needed: SETPOWERMANAGEMENT to set the power state of the display and QUERYESCSUPPORT to query if the SETPOWERMANAGEMENT escape is supported by the driver. The following routine turns the display on or off on systems with display drivers that support the proper escape codes:
//
// Defines and structures taken from pwingdi.h in the Platform Builder
//
#define QUERYESCSUPPORT 8
#define SETPOWERMANAGEMENT 6147
#define GETPOWERMANAGEMENT 6148
typedef enum _VIDEO_POWER_STATE {
VideoPowerOn = 1,
VideoPowerStandBy,
VideoPowerSuspend,
VideoPowerOff
} VIDEO_POWER_STATE, *PVIDEO_POWER_STATE;
typedef struct _VIDEO_POWER_MANAGEMENT {
ULONG Length;
ULONG DPMSVersion;
ULONG PowerState;
} VIDEO_POWER_MANAGEMENT, *PVIDEO_POWER_MANAGEMENT;
//----------------------------------------------------------------------
// SetVideoPower - Turns on or off the display
//
int SetVideoPower (BOOL fOn) {
VIDEO_POWER_MANAGEMENT vpm;
int rc, fQueryEsc;
HDC hdc;
// Get the display dc.
hdc = GetDC (NULL);
// See if supported.
fQueryEsc = SETPOWERMANAGEMENT;
rc = ExtEscape (hdc, QUERYESCSUPPORT, sizeof (fQueryEsc),
(LPSTR)&fQueryEsc, 0, 0);
if (rc == 0) {
// No support, fail.
ReleaseDC (NULL, hdc);
return -1;
}
// Fill in the power management structure.
vpm.Length = sizeof (vpm);
vpm.DPMSVersion = 1;
if (fOn)
vpm.PowerState = VideoPowerOn;
else
vpm.PowerState = VideoPowerOff;
// Tell the driver to turn on or off the display.
rc = ExtEscape (hdc, SETPOWERMANAGEMENT, sizeof (vpm),
(LPSTR)&vpm, 0, 0);
// Always release what you get.
ReleaseDC (NULL, hdc);
return 0;
}
The preceding code queries to see whether the escape is supported by calling ExtEscape with the command QUERYESCSUPPORT. The command being queried is passed in the input buffer. If the SETPOWERMANAGEMENT command is supported, the routine fills in the VIDEO_POWER_MANAGEMENT structure and calls ExtEscape again to set the power state.Although these escape codes allow applications to turn the display on and off, Windows CE has no uniform method to control the brightness of the backlight. Each system has its own OEM-unique method of backlight brightness control. If there's a standard method of brightness control in the future, it will probably be exposed through this same ExtEscape function.
Powering Up the System
When the system is suspended, applications aren't running, so it seems that an application would have no control on when the system resumes. However, there are a few methods for waking a suspended device. First, an application can schedule the system to resume at a given time by using the Notification API discussed in Chapter 11. In addition, OEMs can assign some interrupt conditions so that they power up, or in power management talk resume, the system. An example of this behavior is a system that resumes when it is placed in a synchronization cradle.
Preventing the System from Powering Down
The opposite problem—preventing the system from suspending—can also be an issue. Windows CE systems are usually configured to automatically suspend after some period of no user input. To prevent this automatic suspension, an application can periodically call the following function:
void WINAPI SystemIdleTimerReset (void);
This function resets the timer that Windows CE maintains to monitor user input. If the timer reaches a predefined interval without user input, the system automatically suspends itself. Because the suspend timeout value can be changed, an application needs to know the timeout value so that it can call SystemIdleTimerReset slightly more often. The system maintains three timeout values, all of which can be queried using the SystemParametersInfo function. The different values, represented by the constant passed to SystemParametersInfo, are shown here:
SPI_GETBATTERYIDLETIMEOUT Time from the last user input when the system is running on battery power
SPI_GETEXTERNALIDLETIMEOUT Time from the last user input when the system is running on AC power
SPI_GETWAKEUPIDLETIMEOUT Time from the system auto-powering before the system suspends again
To prevent the system from suspending automatically, you need to query these three values and call SystemIdleTimerReset before the shortest time returned. If any timeout value is 0, that specific timeout is disabled.
The Power Manager
A new, separate power management component was introduced in Windows CE .NET 4.0. This Power Manager replaced much of the functionality that GWES previously performed. The Power Manager defines a series of power states as D0, D1, D2, and D3. These rather cryptic names are then mapped to more friendly names at the system level.For embedded systems, OEMs define the system power states. Examples of power states might be something like On, Idle, and Suspend. Other power states can be defined, such as ScreenOff, InCradle, and OnBattery.From an application perspective, the new Power Manager provides the ability to be notified when the power state changes as well as a uniform method of changing the power state of the system through a series of functions.The power states for the system are defined in the registry. The SDK defines PWRMGR_REG_KEY so that you don't have to know the registry string, but for the times when the constant isn't defined, the Power Manager's registry data is kept at HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Power. The power states are then defined as subkeys under the key State.
Power Notifications
One of the more welcome features of the Power Manager is its ability to notify an application when the power state of the system changes. This ability frees the application from polling the battery state manually to monitor the power. An application can request that the Power Manager send a notification to the application when the power state of the system changes by calling RequestPowerNotifications. The Power Manager then sends the notifications through a message queue that has been previously created by the application.RequestPowerNotifications is prototyped as
HANDLE RequestPowerNotifications (HANDLE hMsgQ, DWORD Flags);
The first parameter is the handle to a message queue that the application has previously created. The second parameter is a series of flags indicating which notifications the application wants to receive. The flags, which can be ORed together, are as follows:
PBT_TRANSITION Receive notifications when the power state changes—for example, when the system goes from On to Suspend.
PBT_RESUME Receive notifications when the system resumes.
PBT_POWERSTATUSCHANGEReceive notifications when the system transitions between AC and battery power.
PBT_POWERINFOCHANGE Receive notifications when the power information, such as the battery level, changes.
POWER_NOTIFY_ALL Receive all power notifications.
The RequestPowerNotifications function returns a handle to the power notification, or NULL if the function fails. The message queue should be created with read access by the application since it will be reading the power notifications from the queue.To receive the notifications, an application should block on the queue handle by using WaitForSingleObject. As discussed in Chapter 10, the handle will be signaled when a notification is placed in the queue. The actual notification is received in the form of a POWER_BROADCAST structure defined as follows:
typedef struct _POWER_BROADCAST {
DWORD Message;
DWORD Flags;
DWORD Length;
WCHAR SystemPowerState[1];
} POWER_BROADCAST, *PPOWER_BROADCAST;
First note that this structure is a variable-length structure. The last field, SystemPowerState, is defined as an array of WCHARs but can be filled with other, nonstring, data. The first field is the identifier of the notification itself. This field is filled with one of the PBT_ flags listed earlier. The Flags field can contain the following flags, depending on the notification being received:
POWER_STATE_ON The system is on.
POWER_STATE_OFF The system is off.
POWER_STATE_CRITICALThe system is performing a critical off.
POWER_STATE_BOOT The system is booting.
POWER_STATE_IDLE The system is idle.
POWER_STATE_SUSPEND The system is suspended.
POWER_STATE_RESET The system is starting after a reset.
The final two parameters are related. The Length field is the length of the data in the SystemPowerState field. The data contained in the SystemPowerState field depends on the notification being sent. For the PBT_TRANSITION notification, the SystemPowerState field contains a string that identifies the new power state. This string is not zero terminated. To terminate the string, use the Length field to determine the length of the string. Note that the Length field is in bytes, while the characters are 2-byte Unicode characters, so to obtain the length of the string in characters, divide the Length field by the size of TCHAR.
For the PBT_POWERINFOCHANGE notification, the SystemPowerState field contains a PPOWER_BROADCAST_POWER_INFO structure defined as follows:
typedef struct _POWER_BROADCAST_POWER_INFO {
DWORD dwNumLevels;
DWORD dwBatteryLifeTime;
DWORD dwBatteryFullLifeTime;
DWORD dwBackupBatteryLifeTime;
DWORD dwBackupBatteryFullLifeTime;
BYTE bACLineStatus;
BYTE bBatteryFlag;
BYTE bBatteryLifePercent;
BYTE bBackupBatteryFlag;
BYTE bBackupBatteryLifePercent;
} POWER_BROADCAST_POWER_INFO, *PPOWER_BROADCAST_POWER_INFO;
Notice that the fields are similar in name and function to many of the fields previously discussed in the SYSTEM_POWER_STATUS_EX2 structure.
Setting the Power State
Functions provided by the Power Manager also allow applications to control the power state. There are two methods for controlling the power. The first method has the application demand a given power setting. The second method has the application request that the power not drop below a given level.An application can request a specific power state by calling the function SetSystemPowerState. This function is prototyped as
DWORD SetSystemPowerState (LPCWSTR psState, DWORD StateFlags,
DWORD Options);
The power state being requested can be specified in either the first or the second parameter of the function. If the first parameter is nonzero, it points to a string that identifies the state being requested. The string should match one of the power states enumerated in the registry.If psState is NULL, the second parameter, StateFlags, defines the requested power state. This parameter is one of the same power states, from POWER_STATE_ON to POWER_STATE_RESET, that were described in the POWER_BROADCAST structure earlier.Of particular interest is the flag POWER_STATE_RESET. This flag requests that the system reset. This method of resetting the system using SetSystemPowerState is much better than directly calling KernelIoControl with the IOCTL command IOCTL_HAL_REBOOT since using SetSystemPowerState will cause the system to flush any buffered data to the file system before the function resets the device.
While calling SetSystemPowerState is a direct method of changing the power state, a more subtle method is to request that the system maintain the minimal power state needed by the application by calling SetPowerRequirement. Using SetSystemPowerState assumes the application knows best, while calling SetPowerRequirement allows the system to optimize the power settings while still meeting the needs of the application. An example of a situation in which SetPowerRequirement is handy occurs when an application is using a serial port and needs the port to stay powered while communication is active. SetPowerRequirement is defined as
HANDLE SetPowerRequirement (PVOID pvDevice,
CEDEVICE_POWER_STATE DeviceState,
ULONG DeviceFlags, PVOID pvSystemState,
ULONG StateFlags);
The first parameter specifies the device that the application needs to remain at a given power state. The DeviceState parameter defines the power state for the device. The enumeration CEDEVICE_POWER_STATE specifies the state, ranging from D0 (meaning that the device must remain fully powered) to D4 (meaning that the device is powered off). The DeviceFlags parameter can be a combination of two flags: POWER_NAME, indicating that the device name is valid; and POWER_FORCE, indicating that the device should remain in that state even if the system suspends. If the pvSystemState is not NULL, it indicates that the power requirement is valid only for the power state named in pvSystemState. The device might not be able to change to the requested state.As soon as possible, the application should remove the power requirement with a call to ReleasePowerRequirement, prototyped as
DWORD ReleasePowerRequirement (HANDLE hPowerReq);
The only parameter is the handle returned from SetPowerRequirement.In the next chapter, I'll continue to explore system issues with a look at Windows CE stream device drivers and services. Although most application developers might never have to write a device driver or a service, knowing how they are put together is rather enlightening. Let's take a look.