Re: [PATCH] Linux ACPI support for battery applet

Michal Ludvig <[email protected]> Thu, 15 Sep 2005 15:28:24 +1200
Newsgroups gmane.comp.handhelds.matchbox
Message-ID <[email protected]>
This is an OpenPGP/MIME signed message (RFC 2440 and 3156)
--===============0585047510==
Content-Type: multipart/signed; micalg=pgp-sha1;
	protocol="application/pgp-signature";
	boundary="------------enig269E4FA03322D51BD36A1C31"

This is an OpenPGP/MIME signed message (RFC 2440 and 3156)
--------------enig269E4FA03322D51BD36A1C31
Content-Type: multipart/mixed; boundary="------------050507080806040604020800"

This is a multi-part message in MIME format.
--------------050507080806040604020800
Content-Type: text/plain; charset=ISO-8859-2
Content-Transfer-Encoding: 7bit

Michal Ludvig wrote:
> Hi again,
> 
> just in case anyone is interested, attached is a patch for
> mb-applet-battery that gathers the battery stats from /proc/acpi instead
> of from libapm. On our system we don't have APM at all and ACPI works
> just fine. Well, except for a short transition period after unplugging
> the AC powercord, where it gives out obviously wrong data. There are
> some simple checks to ignore these obviously too high or too low values.

Err, once again, now with the patch attached ;-)

Michal Ludvig
-- 
* Personal homepage: http://www.logix.cz/michal

--------------050507080806040604020800
Content-Type: text/plain;
 name="battery-acpi.diff"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="battery-acpi.diff"

Index: matchbox-panel/configure.ac
===================================================================
--- matchbox-panel/configure.ac	(revision 1200)
+++ matchbox-panel/configure.ac	(working copy)
@@ -4,7 +4,7 @@
 
 AM_INIT_AUTOMAKE()
 AM_MAINTAINER_MODE
-AM_CONFIG_HEADER([config.h])
+AM_CONFIG_HEADER(config.h)
 
 # Checks for programs.
 AC_GNU_SOURCE
@@ -52,6 +52,9 @@
   [  --enable-debug    enable debug ( verbose ) build],
      enable_debug=$enableval, enable_debug=no )
 
+AC_ARG_ENABLE(acpi-linux,
+  [  --enable-acpi-linux   obtain battery status from /proc/acpi on Linux],
+     enable_acpi_linux=$enableval, enable_acpi_linux=no )
 
 PKG_CHECK_MODULES(LIBMB, libmb >= 1.6,,
 	         AC_MSG_ERROR([*** Required Matchbox Library (libmb) 1.6 not installed ***]))
@@ -79,6 +82,12 @@
   AC_DEFINE(USE_LIBSN, [1], [Has StartupNotification Support])
 fi
 
+dnl ----- Linux ACPI -------------------------------------------------------
+
+if test x$enable_acpi_linux = xyes; then
+  AC_DEFINE(USE_ACPI_LINUX, [1], [Use /proc/acpi to get battery status])
+fi
+
 dnl ----- NLS Fun -----------------------------------------------------------
 
 INTLIBS=""
Index: matchbox-panel/applets/mb-applet-battery.c
===================================================================
--- matchbox-panel/applets/mb-applet-battery.c	(revision 1200)
+++ matchbox-panel/applets/mb-applet-battery.c	(working copy)
@@ -134,7 +134,173 @@
 
 #endif
 
+#ifdef USE_ACPI_LINUX
 
+#define ACPI_PREFIX	"/proc/acpi"
+#define BAT_PREFIX	"battery/BAT0"
+#define AC_PREFIX	"ac_adapter/AC0"
+
+/* ACPI sometimes reports incorrect values, especially 
+ * shortly after unplugging the AC power. Tweak this for 
+ * your hardware. 
+ * See /proc/acpi/battery/BAT0/state:"present rate" for
+ * "normal" values on your system. */
+#define	ACPI_RATE_MIN	300
+#define	ACPI_RATE_MAX	5000
+
+struct avg_consumption {
+	unsigned long avg_values[100];
+	int avg_index;
+	int avg_records;
+	int countdown;
+};
+
+static struct avg_consumption a;
+
+static int
+read_apm(int *values)
+{
+	enum ac_state { AC_UNKNOWN = -1, AC_OFFLINE = 0, AC_ONLINE = 1 };
+	enum bat_state { BAT_UNKNOWN = -1, BAT_CHARGING = 1, BAT_DISCHARGING = 2 };
+
+	struct entry {
+		char *key;
+		char *value;
+		char *unit;
+	};
+
+	struct acpi_status {
+		enum ac_state	ac_state;
+		enum bat_state	bat_state;
+		unsigned long	design_capacity;
+		unsigned long	remaining_capacity;
+		unsigned long	present_rate;
+	};
+
+	static int
+	line_parse(char *line, struct entry *e)
+	{
+		char *ptr;
+		
+		ptr = strchr(line, ':');
+		if (!ptr)
+			return -1;
+		e->key = line;
+		*ptr = 0;
+		while (*++ptr && *ptr == ' ');
+		e->value = ptr;
+		while (*++ptr && *ptr != '\n' && *ptr != ' ');
+		*ptr = 0;
+		if (*++ptr) {
+			e->unit = ptr;
+			while (*++ptr && *ptr != '\n');
+			*ptr = 0;
+		} else
+			e->unit = 0;
+	
+		return 0;
+	}
+
+	FILE *f;
+	char line[1024];
+	struct entry e;
+	struct acpi_status s;
+
+	memset (&s, 0, sizeof(s));
+
+	f = fopen(ACPI_PREFIX "/" AC_PREFIX "/state", "r");
+	if (f) {
+		while (fgets(line, sizeof(line), f)) {
+			if (line_parse(line, &e) < 0)
+				continue;
+			if (strcmp(e.key, "state") == 0) {
+				if(strcmp(e.value, "on-line") == 0) 
+					s.ac_state = AC_ONLINE;
+				else if (strcmp(e.value, "off-line") == 0)
+					s.ac_state = AC_OFFLINE;
+				else 
+					s.ac_state = AC_UNKNOWN;
+			}
+		}
+		fclose(f);
+	}
+
+	f = fopen(ACPI_PREFIX "/" BAT_PREFIX "/info", "r");
+	if (f) {
+		while (fgets(line, sizeof(line), f)) {
+			if (line_parse(line, &e) < 0)
+				continue;
+			if (strcmp(e.key, "design capacity") == 0)
+				sscanf(e.value, "%lu", &s.design_capacity);
+		}
+		fclose(f);
+	}
+
+	f = fopen(ACPI_PREFIX "/" BAT_PREFIX "/state", "r");
+	if (f) {
+		while (fgets(line, sizeof(line), f)) {
+			if (line_parse(line, &e) < 0)
+				continue;
+			if (strcmp(e.key, "remaining capacity") == 0)
+				sscanf(e.value, "%lu", &s.remaining_capacity);
+			else if (strcmp(e.key, "charging state") == 0) {
+				if (strcmp(e.value, "charging") == 0)
+					s.bat_state = BAT_CHARGING;
+				else if (strcmp(e.value, "discharging") == 0)
+					s.bat_state = BAT_DISCHARGING;
+				else
+					s.bat_state = BAT_UNKNOWN;
+			} else if (strcmp(e.key, "present rate") == 0)
+				sscanf(e.value, "%lu", &s.present_rate);
+		}
+		fclose(f);
+	}
+
+	/* Don't estimate TIME_LEFT on current power consumption. Instead 
+	 * compute some average and ignore too low/high values.  */
+	values[TIME_LEFT] = 0;
+	if (s.bat_state != BAT_DISCHARGING && !a.countdown) {
+		memset(&a, 0, sizeof(a));
+		a.countdown = 10;
+	}
+	else if (s.bat_state == BAT_DISCHARGING && s.present_rate) {
+		int num_values = sizeof(a.avg_values)/sizeof(a.avg_values[0]);
+		unsigned long sum = 0;
+		int i;
+
+		if (a.countdown) {
+			a.countdown--;
+			goto bail_out;
+		}
+
+		if (s.present_rate < ACPI_RATE_MIN || s.present_rate > ACPI_RATE_MAX) {
+			goto bail_out;
+		}
+
+		a.avg_values[a.avg_index++] = s.present_rate;
+		a.avg_index %= num_values;
+		if (a.avg_records < num_values)
+			a.avg_records++;
+		if (a.avg_records > 10) {
+			for (i=0; i<a.avg_records; i++)
+				sum += a.avg_values[i];
+			values[TIME_LEFT] = s.remaining_capacity*60/(sum/a.avg_records);
+		}
+bail_out:
+		sum = 0; /* shut up gcc */
+	}
+	
+	values[PERCENTAGE] = s.design_capacity ? s.remaining_capacity*100/s.design_capacity : 0;
+	values[AC_POWER] = s.ac_state;
+
+#if 0
+	printf("AC status: %d\n", values[AC_POWER]);
+	printf("Level:     %d%%\n", values[PERCENTAGE]);
+	printf("Remaining: %d sec\n", values[TIME_LEFT]);
+#endif
+	return 1;
+}
+#else
 #ifdef HAVE_APM_H
 
 static int 
@@ -180,6 +346,7 @@
 }
 
 #endif
+#endif /* Use ACPI */
 
 void 
 fork_exec(char *cmd)

--------------050507080806040604020800--

--------------enig269E4FA03322D51BD36A1C31
Content-Type: application/pgp-signature; name="signature.asc"
Content-Description: OpenPGP digital signature
Content-Disposition: attachment; filename="signature.asc"

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.5 (GNU/Linux)
Comment: Using GnuPG with Thunderbird - http://enigmail.mozdev.org

iD8DBQFDKOpYDDolCcRbIhgRArptAJ0cp0JJi/ro8kudbgllR7F0eVNj/ACgggn5
7cKK4Y8KNg1BIxpgluWI7a8=
=fWeT
-----END PGP SIGNATURE-----

--------------enig269E4FA03322D51BD36A1C31--

--===============0585047510==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

_______________________________________________
Matchbox mailing list
[email protected]
https://handhelds.org/mailman/listinfo/matchbox

--===============0585047510==--