Accessing System Power and Network Status Using SENS

 

Leszynski Group, Inc.

March 2006

Applies to:
   Microsoft Windows 2000
   Microsoft Windows XP
   Microsoft Windows XP Tablet PC Edition 2005
   Microsoft Windows 2003 Server

Summary: This article and sample code demonstrates how to access Windows system power and network status information from managed applications. (11 printed pages)

Click here to download the code sample for this article.

Contents

Introduction
Using SensWrapper
Accessing Static Properties and Methods
Registering for Events
SensWrapper Implementation
Static Properties and Methods
SENS Events
Timer-Polled Events
Development Considerations
Conclusion
Biography

Introduction

This article and sample code demonstrates how to access system power and network status from an application written in C# with Microsoft Visual Studio. The System Event Notification Service (SENS) in the Windows operating system provides developers with information about connection state and battery state. Because SENS uses the COM+ Event System, it cannot be directly referenced from an application using managed code. This article shows how you can create sink objects to implement the SENS interfaces. Doing so enables your applications to subscribe to battery and network events, and you can then handle them as appropriate.

The sample code demonstrates how to:

  • Subscribe to events to be notified when changes occur in network connectivity.
  • Subscribe to events to be notified when changes occur in battery status.
  • Retrieve current status values by making synchronous method calls.

The sample contains the following components:

  • Microsoft.MobilePC.Samples.SensWrapper.dll – a wrapper assembly around SENS that provides system status.
  • SensSample.exe – a Windows Forms-based application that demonstrates the usage of the SensWrapper.dll.

The sample was created in Visual Studio 2003 (.NET Framework 1.1) using C#, but the sample includes compiled binaries that can be demonstrated without Visual Studio.

The sample code can be converted successfully to Visual Studio 2005 without errors but has not been extensively tested with the .NET Framework 2.0.

Using SensWrapper

You use SensWrapper.dll by accessing static properties or methods, or by registering for events.

Accessing Static Properties and Methods

The Sens class exposes the following properties:

  • bool NetworkIsConnected – Returns a value indicating whether network connectivity is present.
  • bool NetworkConnection – Returns a value with bit flags indicating the type(s) of the network connection(s):
    • 1: LAN
    • 2: WAN
    • 4: Other
  • bool PowerIsConnected – Returns a value indicating whether AC power is connected.
  • int BatteryLevel – Returns a value indicating the amount of battery power remaining (0-100) or 255 if unknown.
  • TimeSpan BatteryLife – Returns a value indicating the amount of battery time remaining or negative if unknown.
  • int BatteryStatus – Returns a value indicating the current battery status as follows:
    • 1: High
    • 2: Low
    • 4: Critical
    • 8: Charging
    • 128: Nonexistent
    • 256: Unknown

The following static method is available:

  • bool IsHostReachable(string hostaddress) – Returns a value indicating whether the specified host is reachable through the current network connection(s).

The following code is an example of how SensSample.exe uses the Sens.BatteryLevel property:

int batteryLevel = Sens.BatteryLevel;

Registering for Events

The SensAdvisor class exposes the following events:

  • NetworkConnectionChange – The network connection status or network type has changed.
  • BatteryLevelChange – The battery power level has changed.
  • BatteryStatusChange – The battery status has changed.
  • BatteryLifeChange – The projected remaining battery life has changed.
  • OnBatteryLow – The battery level is below 33%. The "On" prefix is used to prevent name confusion with the ISensOnNow.BatteryLow method.
  • PowerChange – The power status has changed (from AC power to battery or the reverse).

The following code is an example of how SensSample.exe registers for the NetworkConnectionChange event:

public SensSample()
{
          ...

          // Subscribe to events.
          SensAdvisor sa = new SensAdvisor();
          sa.NetworkConnectionChange += new SensAdvisor.NetworkConnectionChangeEventHandler(sa_NetworkConnectionChange);
     ...

}

private void sa_NetworkConnectionChange (object sender, SensAdvisor.NetworkConnectionChangeEventArgs e)
{
     ...
}

SensWrapper Implementation

The following sections discuss implementation details for the SensWrapper class.

Static Properties and Methods

The static properties and methods in SensWrapper.dll call directly into either Kernel32.dll or SensApi.dll to get the desired system information. For example, the Sens.BatteryLevel property is implemented as follows:

/// <summary>
/// Gets a value indicating the percentage of battery power remaining (0-100) 
/// or 255 if unknown.
/// </summary>
/// <exception cref="System.ComponentModel.Win32Exception">
/// Thrown when the call to GetSystemPowerStatus fails.
/// </exception>
public static int BatteryLevel
{
   get
   {
      SYSTEM_POWER_STATUS sps = new SYSTEM_POWER_STATUS();
      if (!NativeMethods.GetSystemPowerStatus(out sps))
         throw new System.ComponentModel.Win32Exception();

      return (sps.BatteryLifePercent);
   }
}

The Sens.BatteryLevel property gets power status information from the NativeMethods class, which implements external methods in SensApi.dll:

//
// NativeMethods
//
// Implements external methods from the SENS and Windows APIs.
//
private sealed class NativeMethods
{
   ...
   //
   // GetSystemPowerStatus
   //
   // Retrieves the power status of the system. 
   // The status indicates whether the system is running
   // on AC or DC power, whether the battery is currently charging, 
   // and how much battery life remains.
   //
   // Parameters:
   //    lpSystemPowerStatus - [out] Pointer to a SYSTEM_POWER_STATUS structure that receives status information. 
   //
   [DllImport("Kernel32.dll", SetLastError=true)]
   public extern static bool GetSystemPowerStatus(out SYSTEM_POWER_STATUS lpSystemPowerStatus);
}

SENS Events

SENS provides events for changes in several system status properties. However, some SENS properties that are useful do not fire events. To address this, the sample adds a timer to poll for status changes in certain properties where it cannot register for SENS events. This enables an application to register for events based on any of the system status values reported by SensWrapper.dll, regardless of whether SENS actually provides an equivalent event.

The COM+ Events Service is an automated, loosely-coupled event system available since Windows 2000. This service stores event information from different publishers in the COM+ catalog; subscribers can query this catalog and select the events they want to subscribe to. SENS publishes events to COM+. The sample SensWrapper.dll subscribes to the SENS events through COM+.

To begin subscribing, we added references for ComAdmin.dll and SensEvents.dll to the SensWrapper.dll project. To add the references manually, add references to COM+ 1.0 Admin Type Library and SENS Events Type Library located on the COM tab in the Add Reference dialog within Visual Studio. For detailed information about the COMAdmin classes, see Overview of the COMAdmin Objects, in the MSDN Library.

When you create the reference to the COMAdmin classes, Visual Studio adds this using statment to the project, as seen in SensAdvisor.cs:

using COMAdmin;

Next, the SensAdvisor class inherits the SensEvents interfaces that COM+ calls when the events occur:

public sealed class SensAdvisor : SensEvents.ISensNetwork, SensEvents.ISensOnNow
{
     ...
}

The following code, from the constructor and the SubscribeToEvent method, illustrates the actual subscription process. First, obtain an ICatalogCollection object representing the TransientSubscriptions collection. The subscription is considered transient because it must be renewed each time SensWrapper.dll runs:

// Subscribe to SENS events.
COMAdminCatalogClass comAdmin = new COMAdminCatalogClass();
ICatalogCollection subCollection = (ICatalogCollection) 
     comAdmin.GetCollection("TransientSubscriptions");

Next, create a new ICatalogObject object by using the ICatalogCollection.Add method. The values of this object are set to the information that is relevant to the event that it will subscribe to. Note that value PerUser is set to True; if you leave this value set to its default value of False, your application needs local Administrator privileges to accomplish the transient subscription.

After all of the values are set, add the object to the transient subscriptions collection with the ICatalogCollection.SaveChanges method:

//
// SubscribeToEvent
//
// Subscribe to COM+ SENS events.
//
// Parameters:
//    subCollection - The COM+ catalog collection that holds the transient subscription object.
//    methodName - The name of the method to subscribe.
//    guidString - The CLSID for the event class.
//
private void SubscribeToEvent(ICatalogCollection subCollection, string methodName, string guidString)
{
   ICatalogObject catalogObject = (ICatalogObject) subCollection.Add();

   // Specify the parameters of the desired subscription.
   catalogObject.set_Value("EventCLSID", guidString);
   catalogObject.set_Value("Name", "Subscription to " + methodName + " event");
   catalogObject.set_Value("MethodName", methodName);
   catalogObject.set_Value("SubscriberInterface", this);
   catalogObject.set_Value("Enabled", true);
   // This setting allows subscriptions to work for non-Administrator users.
   catalogObject.set_Value("PerUser", true);  

   // Save the changes made to the transient subscription collection.
   subCollection.SaveChanges();
}

Repeat this process for each of the SENS events of interest. When a subscribed SENS event occurs, COM+ calls the appropriate methods from the SENS events interfaces exposed by SensAdvisor. These methods fire the SensAdvisor events that the parent application is listening for, as in the following example:

/// <summary>
/// Receives the SENS ConnectionMade event.
/// </summary>
/// <param name="bstrConnection">
/// The name of the network connection.
/// </param>
/// <param name="ulType">
/// The network connection type as CONNECTION_LAN (0) or CONNECTION_WAN (1).
/// </param>
/// <param name="lpQOCInfo">
/// A structure containing Quality of Connection information.
/// </param>
/// <exclude/>
public void ConnectionMade(string bstrConnection, uint ulType, ref SensEvents.SENS_QOCINFO lpQOCInfo)
{
   // Fire the event if there are any subscriptions to it.
   if (NetworkConnectionChange != null)
   {
      NetworkConnectionChangeEventArgs args = new NetworkConnectionChangeEventArgs();
      args.IsConnected = true;
      args.ConnectionName = bstrConnection;
      args.ConnectionType = (Sens.NetworkConnectionType)ulType;
      NetworkConnectionChange(this, args);
   }
}

Timer-Polled Events

SenAdvisor events that are not directly driven from events in SENS itself are derived instead by polling the status values during the Timer.Elapsed event and firing an appropriate event when the code detects a value change. The timer is initialized as follows in the constructor:

// Set up a timer to poll for new status values.
// The polling interval is 2000 milliseconds.
timer.Interval = 2000; 
// Disable autoreset to avoid reentrancy problems.
timer.AutoReset = false;
timer.Elapsed += new System.Timers.ElapsedEventHandler(TimerTick);
timer.Start();

Set the Timer.Interval property as desired for faster or slower polling. Note that Timer.AutoReset is set to false to prevent reentrancy problems. This enables you to set the Interval to be as short as desired without worrying about an event subscriber failing to return before the next timer tick occurs. Such reentrancy can be a problem, especially the first time that an application runs and the code in the event handler is JIT-compiled.

When the Elapsed event occurs, an appropriate event is fired if the new values differ from the previous values. At the end of the Elapsed event handler, start the timer again:

//
// TimerTick
//
// Occurs when the Timer interval elapses (fires its Elapsed event).
// It supplements the events that SENS provides by firing events when current 
// values differ from previous values for status items that SENS doesn't provide 
// events for, such as battery level.
//
private void TimerTick(object sender, System.Timers.ElapsedEventArgs e)
{
   // Check battery level.
   int batteryLevel = Sens.BatteryLevel;
   if (batteryLevel != previousBatteryLevel)
   {
      previousBatteryLevel = batteryLevel;
      if (BatteryLevelChange != null)
      {
         BatteryLevelChangeEventArgs args = new BatteryLevelChangeEventArgs();
         args.BatteryLevel = batteryLevel;
         BatteryLevelChange(this, args);
      }
   }

   // Check battery status.
   Sens.BatteryStatusValue batteryStatus = Sens.BatteryStatus;
   if (batteryStatus != previousBatteryStatus)
   {
      previousBatteryStatus = batteryStatus;
      if (BatteryStatusChange != null)
      {
         BatteryStatusChangeEventArgs args = new BatteryStatusChangeEventArgs();
         args.BatteryStatus = batteryStatus;
         BatteryStatusChange(this, args);
      }
   }

   // Check battery life.
   TimeSpan batteryLife = Sens.BatteryLife;
   if (batteryLife != previousBatteryLife)
   {
      previousBatteryLife = batteryLife;
      if (BatteryStatusChange != null)
      {
         BatteryLifeChangeEventArgs args = new BatteryLifeChangeEventArgs();
         args.BatteryLife = batteryLife;
         BatteryLifeChange(this, args);
      }
   }

   // Restart the timer because it's not in autoreset mode.
   timer.Start();
}

Development Considerations

When using SensWrapper.dll in your applications, test the component on the specific computer hardware to ensure that it provides the expected information about connection state and battery status. SensWrapper.dll is dependent on the COM+ Event System and System Event Notification services; ensure that these services are started on any development or destination computer using the component.

Some additional issues to consider when using SensWrapper.dll are:

  • Kernel32 GetSystemPowerStatus – The battery state (high, low, critical, or other) reported by GetSystemPowerStatus does not synchronize with that reported by the shell or by PowerCfg.cpl. This method transitions to low or critical states at different values than the Windows Power Options utility, and it may sometimes report unknown when the battery is near the 50% capacity mark.
  • SensApi IsNetworkAlive – Test this function on your network to determine how the return values of LAN and WAN behave in your environment (the return value of AOL is deprecated).
  • SensApi IsDestinationReachable – This function is marginally useful, because a programmatic ping can block for up to 20 minutes for endpoints that don't respond to ICMP echo (such as microsoft.com). There is no easy way to determine if a certain TCP endpoint is reachable or not unless you actually attempt to establish a connection with it. Many sites now block ICMP echo requests to mitigate denial of service attacks. In addition, the speed parameters do not appear to measure actual bandwidth, they simply surface whatever the NIC hardware states as its ideal bandwidth.
  • SensApi ISensOnNow – The BatteryLow event fires at a hard-coded threshold of 33% and does not synchronize with the alert levels set in Windows Power Options utility.
  • SensApi ISensNetwork – It is unclear what triggers the DestinationReachable event.

Conclusion

The System Events Notification Service in Windows can provide your mobilized custom applications with useful system status information, but the SENS event architecture is difficult to use from managed code because it is built on COM+. Further, the architecture does not provide SENS events for all of the information that SENS is aware of and that your application might want to consume. The SensWrapper.dll sample helps your custom applications easily tap in to battery and network status information in Windows, both synchronously and asynchronously.

Biography

Leszynski Group is a solution provider and independent software developer specializing in development tools, Tablet PC applications, and mobilized databases. Leszynski Group is a regular contributor of MSDN content, and the creator of popular utilities such as the Snipping Tool and Physics Illustrator.