Tuesday, September 8, 2009

Convergence 09: Developing Power Aware Application using D-Bus

Introduction


Computer power consumption has become a passionate subject; hardware engineers trying to minimize the power consumed by devices, and software engineers trying to optimize their code to reduce the power consumed by their applications. Power is an even more important issue with Mobile Internet Devices (MIDs) since the small form factor limits the size of the battery. Applications running on MID's must therefore be very sensitive to power, and use power information to make smart decisions. For example, before beginning a lengthy transaction (such as downloading a video, or running an update), an application should check to see if the device is running on external power, and if not, determine whether or not there is sufficient power remaining to complete the transaction.

Background on D-Bus

D-Bus is a fast, lightweight message bus system which allows applications to communicate each other (Inter-process communication) [2]. It has a so-called D-Bus message bus (dbus deamon) which can accept messages from multiples applications and forward messages to them. D-Bus can be used as low-level API or higher level binding, such as Qt, Python*, Java*, C#, or Perl. This paper shows an application written in C using Glib bindings. Glib is the base library of GNOME, it provides an object system, object-based and event-driven programming.

Two buses are defined in D-Bus: system bus and session bus. The system bus allows communication between an application and the operating system while the session bus is designed to serve the communication between two applications.

In order to use D-Bus, we need to explore some concepts about D-Bus. First, an object is an endpoint on the bus, an object is created by an application in the context that the application's connection to the bus (either system bus or session bus). Objects have names, and these names are called object paths. A proxy is an object on the bus that can be accessed through references. We can find an object by searching the bus name and the object living in that bus connection. Once we find it, we usually keep a proxy to that object so we can refer to that object again without searching again.

An object can perform some specific operations. Each operation is referred as method. Thus, a client can send a request to an object and ask the object to invoke a method. The object then executes the method (if the method exists) and the result is sent back to the client. If a method requires input parameters, these parameters have to be passed with the request. The result can be one or more output parameters, these parameters are sent back to the client in the reply message.

An object can also emit an event, or signal. When a signal is generated by an object, it will be broadcasted to any observers who are interested in that particular signal. Signals can carry parameters too. Methods and Signals are embedded members of an object. They can be grouped. An Interface of an object is just a group of its members. An object can declare one or many interfaces - its members are then classified in these interfaces.

Instead of forming a D-Bus method call message and sending to the remote object using Glib bindings, one can instantiate a proxy object representing the remote object and then just invoke the methods of that object.

In order to generate dbus-glib binding code, we can use a tool called dbus-binding-tool. We first create an XML file, referred as Introspection XML file, in which we describe the methods and signals. An example of the introspection XML is shown below:

  1. "1.0" encoding="UTF-8" ?>

  2. "-//freedesktop//DTD D-Bus Object Introspection 1.0//EN"
  3. "http://standards.freedesktop.org/dbus/1.0/introspect.dtd">
  4. "/org/moblin/Platform">
  5. "org.moblin.Platform.Power">
  6. "org.freedesktop.DBus.GLib.CSymbol" value="org_moblin_platform_power"/>
  7. "IsUsingExternalPowerSource">
  8. "org.freedesktop.DBus.GLib.CSymbol" value="isUsingExternalPowerSource" />
  9. "b" name="usingExternalPowerSource" direction="out" />

  10. "GetTimeRemaining">
  11. "org.freedesktop.DBus.GLib.CSymbol" value="getTimeRemaining"/>
  12. "i" name="minutesRemaining" direction="out" />

  13. "GetPercentRemaining">
  14. "org.freedesktop.DBus.GLib.CSymbol" value="getPercentRemaining"/>
  15. "u" name="percentRemaining" direction="out" />





In this introspection XML file (named MIDPlatformSvcInterfaceDefinition.xml), an interface called org.moblin.Platform.Power defines three methods: IsUsingExternalPowerSource, GetTimeRemaining and GetPerCentRemaining. The output of the first method is a Boolean value. The output of the second method is an integer value representing the value in minutes, while the output of the third method is an unsigned integer value representing the percentage.

Below are primary steps to implement a service with methods to be exposed. More details are explained in [3].
  • Run dbus-binding-tool for an introspection XML file to generate dbus-glib binding code
  • Create a simple GObject for D-Bus
  • Implement the instance initialization
  • Implement the methods
  • Register the object to the D-Bus
To call the methods implemented on a remote object, a client needs to follow the following steps:
  • Connect to the bus
  • Create a proxy object
  • Invoke the methods on the proxy object

Using D-Bus as a Communication Channel


We designed the power service application which uses D-Bus as a communication channel to send information on battery. Other applications which are interested in power awareness can use D-Bus to get power information by querying the methods that the power service provided. These applications can also listen for the events that the power service generated. Figure 1 illustrates the usage model:

Figure 1. Usage Model: A Client Registers to D-Bus to Get the Information Provided by the MISPlatformSvc



Gathering Battery Information


We will present the implementation of three methods that are part of a simple library that provides battery information. The first method queries the percent of battery power remaining (getPercentRemaining). This method returns the percent of power currently available in the battery.

The second method queries the time remaining until the MID runs out of battery power (getTimeRemaining). This method returns the remaining time in minutes.

The third method queries whether or not the MID is using an external power source (IsUsingExternalPowerSource). This method returns a Boolean: TRUE means the MID is currently using an external power source (e.g., AC), FALSE means that the MID is using its battery (DC).

The first method calculates the percent of battery power remaining in a MID. In Linux, the file /proc/acpi/ contains power-related information. In particular, the file /proc/acpi/battery/BAT1/state contains the current remaining capacity in the field "remaining capacity". The file /proc/acpi/battery/BAT1/info contains the maximum capacity information in the "last full capacity" field. From these entries, we can deduce the percent of power remaining as shown below:

  1. gboolean computePercentPowerRemaining(guint *result)
  2. {
  3. DIR *d;
  4. struct dirent *de;
  5. char *acpi_path = "/proc/acpi";
  6. char *device_type = "battery";
  7. char *stateFile = "state";
  8. char *infoFile = "info";
  9. FILE *file;
  10. char filename[PATH_LENGTH];
  11. char line[LINE_LENGTH];
  12. *result = 0;
  13. double power_left = 0.0;
  14. double power_full = 0.0;
  15. gboolean rc = FALSE;

  16. // First, check if the ACPI path exists
  17. if (chdir(acpi_path) <>
  18. {
  19. dbg("ACPI is not supported (\"%s\").\n", acpi_path);
  20. return rc;
  21. }
  22. else
  23. dbg("Path %s is found \n", acpi_path);

  24. // Check if battery directory exists
  25. if (chdir(device_type) <>
  26. {
  27. dbg("No support for device type: %s\n", device_type);
  28. return rc;
  29. }
  30. else
  31. dbg("Device %s is supported \n", device_type);

  32. // /proc/acpi/battery is found. Now search for available batteries
  33. d = opendir(".");
  34. if (!d) {
  35. dbg("Cannot open directory!\n");
  36. return rc;
  37. }

  38. while ((de = readdir(d))) {
  39. // Search for real directory ("BAT0", ...), not ".", ".."
  40. if (!strcmp(de->d_name, "."))
  41. continue;

  42. if (!strcmp(de->d_name, ".."))
  43. continue;

  44. dbg("device found: %s\n", de->d_name);

  45. // Open file "state".
  46. sprintf(filename, "%s/%s/%s/%s", acpi_path, device_type, de->d_name, stateFile);
  47. file = fopen(filename, "r");
  48. if (!file)
  49. {
  50. closedir(d);
  51. return rc;
  52. }

  53. memset(line, 0, LINE_LENGTH);
  54. while (fgets(line, LINE_LENGTH, file) != NULL) {
  55. if (strstr(line, "remaining capacity:") &&
  56. (strstr(line, "mWh") || strstr(line, "mAh")))
  57. {
  58. // Search the value
  59. char *c;

  60. c = strchr(line, ':');
  61. c++;

  62. power_left = strtoull(c, NULL, 10); // in mWh or mAh

  63. // Cannot perform conversion
  64. if ((0 == power_left) && (EINVAL == errno))
  65. {
  66. closedir(d);
  67. return rc;
  68. }
  69. break;
  70. }
  71. }

  72. fclose(file);

  73. // Open file "info".
  74. sprintf(filename, "%s/%s/%s/%s", acpi_path, device_type, de->d_name, infoFile);
  75. file = fopen(filename, "r");
  76. if (!file)
  77. {
  78. closedir(d);
  79. return rc;
  80. }

  81. memset(line, 0, LINE_LENGTH);
  82. while (fgets(line, LINE_LENGTH, file) != NULL) {
  83. if (strstr(line, "last full capacity:") &&
  84. (strstr(line, "mWh") || strstr(line, "mAh")))
  85. {
  86. // Search the value
  87. char *c;

  88. c = strchr(line, ':');
  89. c++;

  90. power_full = strtoull(c, NULL, 10); // in mWh or mAh

  91. // Cannot perform conversion
  92. if (0 == power_full)
  93. {
  94. closedir(d);
  95. return rc;
  96. }

  97. *result = (power_left/power_full)*100; // in %
  98. break;
  99. }
  100. }

  101. rc = TRUE;
  102. fclose(file);

  103. // Exit while loop
  104. break;
  105. }
  106. closedir(d);

  107. return rc;
  108. }


The second method queries the time remaining until the MID is out battery power (getTimeRemaining). This method returns the remaining time in minutes. As mentioned above, the file /proc/acpi/battery/BAT1/state contains the current remaining capacity in the "remaining capacity" field. The "present rate" field in the same file contains the rate at which the battery capacity is changing. This field is significant only if the battery is discharging. From this information, we can deduce the remaining time in minutes as shown below:

  1. int computeTimeRemaining(void)
  2. {
  3. DIR *d;
  4. struct dirent *de;

  5. char *acpi_path = "/proc/acpi";
  6. char *device_type = "battery";
  7. char *stateFile = "state";

  8. FILE *file;
  9. char filename[PATH_LENGTH];
  10. char line[LINE_LENGTH];
  11. int result = -1;
  12. double power_left = 0.0;
  13. double power_full = 0.0;
  14. double dischargeRate = 0.0;
  15. int find = 0;

  16. // First, check if the ACPI path exists
  17. if (chdir(acpi_path) <>
  18. {
  19. dbg("ACPI is not supported (\"%s\").\n", acpi_path);
  20. return result;
  21. }
  22. else
  23. dbg("Path %s is found \n", acpi_path);

  24. // Check if battery directory exists
  25. if (chdir(device_type) <>
  26. {
  27. dbg("No support for device type: %s\n", device_type);
  28. return result;
  29. }
  30. else
  31. dbg("Device %s is supported \n", device_type);

  32. // /proc/acpi/battery is found. Now search for available batteries
  33. d = opendir(".");
  34. if (!d) {
  35. dbg("Cannot open directory!\n");
  36. return result;
  37. }

  38. while ((de = readdir(d))) {
  39. // Search for real directory ("BAT0", ...), not ".", ".."
  40. if (!strcmp(de->d_name, "."))
  41. continue;

  42. if (!strcmp(de->d_name, ".."))
  43. continue;

  44. dbg("device found: %s\n", de->d_name);

  45. // Open file "state".
  46. sprintf(filename, "%s/%s/%s/%s", acpi_path, device_type, de->d_name, stateFile);
  47. file = fopen(filename, "r");
  48. if (!file)
  49. {
  50. closedir(d);
  51. return result;
  52. }

  53. memset(line, 0, LINE_LENGTH);

  54. while (fgets(line, LINE_LENGTH, file) != NULL) {
  55. if (strstr(line, "charging state:") &&
  56. strstr(line, "discharging"))
  57. {
  58. find++;
  59. }

  60. if (strstr(line, "present rate:") &&
  61. (strstr(line, "mW") || strstr(line, "mA")))
  62. {
  63. // Search the value
  64. char *c;

  65. c = strchr(line, ':');
  66. c++;

  67. dischargeRate = strtoull(c, NULL, 10); // in mW or mA

  68. // Cannot perform conversion
  69. if ((0 == dischargeRate) && (EINVAL == errno))
  70. {
  71. closedir(d);
  72. return result;
  73. }

  74. find++;
  75. }

  76. if (strstr(line, "remaining capacity:") &&
  77. (strstr(line, "mWh") || strstr(line, "mAh")))
  78. {
  79. // Search the value
  80. char *c;

  81. c = strchr(line, ':');
  82. c++;

  83. power_left = strtoull(c, NULL, 10); // in mWh or mAh

  84. // Cannot perform conversion
  85. if ((0 == power_left) && (EINVAL == errno))
  86. {
  87. closedir(d);
  88. return result;
  89. }

  90. find++;
  91. }


  92. }

  93. fclose(file);

  94. // Exit while loop
  95. break;
  96. }
  97. closedir(d);

  98. if (find == 3)
  99. {
  100. result = 0;

  101. if (dischargeRate > 0)
  102. result = (power_left/dischargeRate)*60; // convert to minutes
  103. }

  104. return result;
  105. }


The third method queries whether or not the MID is using an external power source. The information in the file /proc/acpi/ac_adaptor/ACAD/state shows the status of the battery. Below shows the implementation.

  1. int computePowerSource()
  2. {
  3. DIR *d;
  4. struct dirent *de;
  5. char *acadaptor_path = "/proc/acpi/ac_adapter";
  6. char *stateFile = "state";
  7. FILE *file;
  8. int result = -1;
  9. char filename[PATH_LENGTH];

  10. // First, check if the ac_adaptor path exists
  11. if (chdir(acadaptor_path) <>
  12. {
  13. return result;
  14. }

  15. // /proc/acpi/ac_adaptor is found. Now search for available entry
  16. d = opendir(".");
  17. if (!d) {
  18. return result;
  19. }

  20. while ((de = readdir(d))) {
  21. char line[LINE_LENGTH];
  22. // Search for real directory (not ".", ".."
  23. if (!strcmp(de->d_name, "."))
  24. continue;

  25. if (!strcmp(de->d_name, ".."))
  26. continue;

  27. sprintf(filename, "%s/%s/%s", acadaptor_path, de->d_name, stateFile);
  28. file = fopen(filename, "r");
  29. if (!file)
  30. {
  31. closedir(d);
  32. return result;
  33. }

  34. memset(line, 0, LINE_LENGTH);
  35. while (fgets(line, LINE_LENGTH, file) != NULL) {
  36. if (strstr(line, "state:") && strstr(line, "off-line"))
  37. result = 0; // AC (battery)
  38. else if (strstr(line, "state:") && strstr(line, "on-line"))
  39. result = 1; // DC
  40. }

  41. fclose(file);
  42. }

  43. closedir(d);

  44. return result;
  45. }


Putting Things Together


First, the introspection XML in previous section clearly defined the three methods for battery. We can now generate a D-Bus binding header file using the D-Bus binding tool.

  1. % dbus-binding-tool --mode=glib-server -–prefix=MIDPlatformSvc --output=MIDPlatformSvc-dbus-glue.h MIDPlatformSvcInterfaceDefinition.xml


Then, we create a platform service object by following standard steps in [3]. We don't show it here since it is straightforward. In the final step, we just glue the three methods above to the implementation.

Each interface method has three parameters: the called object, the return value, and the error. Inside the interface method we glue the return value to its implementation.

  1. gboolean getPercentRemaining(MIDPlatformSvc *obj, guint *percentRemaining, GError **error)
  2. {
  3. gboolean rc = FALSE;

  4. rc = computePercentPowerRemaining(percentRemaining);

  5. if (rc)
  6. return TRUE;
  7. else
  8. {
  9. g_set_error(error,
  10. G_FILE_ERROR,
  11. G_FILE_ERROR_NOENT,
  12. "Failed to open file /proc/acpi/battery");
  13. return FALSE;
  14. }
  15. }

  16. gboolean getTimeRemaining(MIDPlatformSvc *obj, gint *timeRemaining, GError **error)
  17. {
  18. *timeRemaining = computeTimeRemaining();

  19. if (*timeRemaining > 0)
  20. {
  21. return TRUE;
  22. }
  23. else if (*timeRemaining == 0)
  24. {
  25. g_set_error(error,
  26. G_FILE_ERROR,
  27. G_FILE_ERROR_NOENT,
  28. "Cannot determine since information is missing!");
  29. return FALSE;
  30. }
  31. else
  32. {
  33. g_set_error(error,
  34. G_FILE_ERROR,
  35. G_FILE_ERROR_NOENT,
  36. "Failed to open file or directory");
  37. return FALSE;
  38. }
  39. }

  40. gboolean isUsingExternalPowerSource(MIDPlatformSvc *obj, gboolean *onExternalSource, GError **error)
  41. {
  42. int t = computePowerSource();

  43. if (t == 0)
  44. {
  45. *onExternalSource = FALSE;
  46. return TRUE;
  47. }
  48. else if (t == 1)
  49. {
  50. *onExternalSource = TRUE;
  51. return TRUE;
  52. }
  53. else
  54. {
  55. g_set_error(error,
  56. G_FILE_ERROR,
  57. G_FILE_ERROR_NOENT,
  58. "Failed to open file or directory");

  59. return FALSE;
  60. }
  61. }


A Test Client


To test the interface power, we write a simple test client using D-Bus as well. The test client invokes the remote methods of object MIDPlatformSvc in order to retrieve battery information on the MID.

  1. #define PLATFORM_SERVICE "org.moblin.Platform"
  2. #define PLATFORM_PATH "/org/moblin/Platform"
  3. #define PLATFORM_POWER_IF "org.moblin.Platform.Power"
  4. #define POWER_SRC_CHANGED_SIGNAL "PowerSourceChanged"

  5. void queryMethod(char* param)
  6. {
  7. DBusGConnection* conn;
  8. GError *error;
  9. DBusGProxy *platformProxy;
  10. DBusError err;

  11. printf("Calling remote method: %s\n", param);

  12. g_type_init();
  13. error = NULL;
  14. conn = dbus_g_bus_get(DBUS_BUS_SESSION, &error);

  15. // Create the proxy Platform
  16. platformProxy = dbus_g_proxy_new_for_name(conn,
  17. PLATFORM_SERVICE, // target for the method call
  18. PLATFORM_PATH, // object to call on
  19. PLATFORM_POWER_IF); // interface to call on

  20. // Now call the metjod "IsUsingExternalPowerSource"
  21. if (param == "GetPercentRemaining")
  22. {
  23. uint result;

  24. if (!dbus_g_proxy_call(platformProxy, param, &error,
  25. G_TYPE_INVALID,
  26. G_TYPE_UINT, &result,
  27. G_TYPE_INVALID))
  28. {
  29. g_printerr("Failed to call remotely: %s\n",
  30. error->message);
  31. g_error_free(error);
  32. }
  33. else
  34. printf ("Remote call successes with returned value: %d%\n", result);
  35. }
  36. else if (param == "GetTimeRemaining")
  37. {
  38. int result;

  39. if (!dbus_g_proxy_call(platformProxy, param, &error,
  40. G_TYPE_INVALID,
  41. G_TYPE_INT, &result,
  42. G_TYPE_INVALID))
  43. {
  44. g_printerr("Failed to call remotely: %s\n",
  45. error->message);
  46. g_error_free(error);
  47. }
  48. else
  49. {
  50. printf ("Remote call successes with returned value: %d minutes\n",result);
  51. }
  52. }
  53. else if (param == "IsUsingExternalPowerSource")
  54. {
  55. gboolean result;

  56. if (!dbus_g_proxy_call(platformProxy, param, &error,
  57. G_TYPE_INVALID,
  58. G_TYPE_BOOLEAN, &result,
  59. G_TYPE_INVALID))
  60. {
  61. g_printerr("Failed to call remotely: %s\n",
  62. error->message);
  63. g_error_free(error);
  64. }
  65. else
  66. printf ("Remote call successes with returned value: %s\n", result? "TRUE" : "FALSE");
  67. }

  68. }

  69. int main(int argc, char** argv)
  70. {
  71. char* methodName[3] = {"GetPercentRemaining",
  72. "GetTimeRemaining",
  73. "IsUsingExternalPowerSource"};

  74. int i;

  75. for (i = 0; i <= 2; i++)
  76. queryMethod(methodName[i]);

  77. return 0;
  78. }


Below is the result when running the test client.

Calling remote method: IsUsingExternalPowerSource

% ./clientTester
Calling remote method: GetPercentRemaining
Remote call successes with returned value: 96%
Calling remote method: GetTimeRemaining
Remote call successes with returned value: 235 minutes
Calling remote method: IsUsingExternalPowerSource
Remote call successes with returned value: FALSE


Conclusion


Because the MID form factor limits the size of the battery, applications that run on the platform must be aware of power state information in order to make smart decisions. Knowing how much power is remaining, how much time is remaining, and whether or not the system is running on external power can help software make wise use of the MID's resources. D-Bus is a lightweight Remote Procedure Call (RPC) which is suitable for many applications running on desktop. Because D-Bus is simple and relative small, yet powerful, we can adapt this technology for Mobile Internet Device (MID). In this paper, we introduced a platform service for MID using D-Bus technology. We showed how to implement three power-related methods provided by the MIDPlatformSvc. A sample test client is also shown to illustrate how we can use D-Bus to invoke these methods. As D-Bus not only supports methods, it can also support signals; we can also extend this application to include signals. In the feature, this platform service can be extended to include other interfaces such as network, location, CPU utilization...

References


[1] D-Bus, http://www.freedesktop.org/wiki/Software/dbus
[2] D-Bus Tutorial, http://dbus.freedesktop.org/doc/dbus-tutorial.html
[3] http://www.moblin.org/toolkits/basicDevGuides/mobLinux/toolkits_DevGds_mobLinux_createDBUS.php
[4] Mobile and Internet Linux Project, http://moblin.org/



No comments:

Post a Comment