Hi tech@ !
A couple of times now I didn't notice when my laptop battery reached the 0%
remaining capacity. I am not aware of any "tool" in base that could issue
a beep or nice sound in case of critical battery.
Currently, apmd reports battery and power events to syslog.
Below is a patch to let apmd report battery state, battery life and
power state as parameters to an action script (/etc/apm/powerchange).
This makes the powerup and powerdown scripts obsolete.
I also included my powerchange script as an example.
Any feedback is welcome.
Holger
;-se
Index: apmd.8
===================================================================
RCS file: /cvs/src/usr.sbin/apmd/apmd.8,v
retrieving revision 1.43
diff -u -r1.43 apmd.8
--- apmd.8 28 Oct 2010 18:21:20 -0000 1.43
+++ apmd.8 1 Dec 2010 21:55:38 -0000
@@ -153,32 +153,29 @@
in the requested state after running the configuration script and
flushing the buffer cache.
.Pp
-Actions can be configured for the following five transitions:
+Actions can be configured for the following four transitions:
suspend,
standby,
resume,
-powerup,
and
-powerdown.
+powerchange.
The suspend and standby actions are run prior to
.Nm
performing any other actions (such as disk syncs) and entering the new
state.
-The resume program is run after resuming from a stand-by or
-suspended state.
-The powerup and powerdown programs are run after the power status (AC
-connected or not) changes, as well as after a resume (if the power
-status changed in the mean time).
+The resume action is run after resuming from a stand-by or suspended state.
+The powerchange action is run after a change of the power status (AC
+connected or not) or the battery status (remaining capacity),
+as well as after a resume (if the power status changed in the mean time).
.Sh FILES
-.Bl -tag -width "/etc/apm/powerdownXX" -compact
+.Bl -tag -width "/etc/apm/powerchangeXX" -compact
.It /dev/apmctl
Default device used to control the APM kernel driver.
.Pp
.It /etc/apm/suspend
.It /etc/apm/standby
.It /etc/apm/resume
-.It /etc/apm/powerup
-.It /etc/apm/powerdown
+.It /etc/apm/powerchange
These files contain the host's customized actions.
Each file must be an executable binary or shell script.
A single program or script can be used to control all transitions
@@ -187,9 +184,13 @@
suspend,
standby,
resume,
-powerup,
or
-powerdown.
+powerchange.
+.Nm
+passes three arguments (numeric values) to the powerchange script:
+the battery state (0=high, 1=low, 2=critical, 4=abscent),
+remaining battery life (0-100 in %)
+and power state (0=AC disconnected, 1=AC connected).
.Pp
.It /var/run/apmdev
Default
Index: apmd.c
===================================================================
RCS file: /cvs/src/usr.sbin/apmd/apmd.c,v
retrieving revision 1.56
diff -u -r1.56 apmd.c
--- apmd.c 2 Apr 2010 04:12:46 -0000 1.56
+++ apmd.c 1 Dec 2010 21:55:38 -0000
@@ -81,7 +81,7 @@
void stand_by(int ctl_fd);
void setperf(int new_perf);
void sigexit(int signo);
-void do_etc_file(const char *file);
+void do_etc_file(const char *file, struct apm_power_info *pinfo);
void sockunlink(void);
/* ARGSUSED */
@@ -472,7 +472,7 @@
void
suspend(int ctl_fd)
{
- do_etc_file(_PATH_APM_ETC_SUSPEND);
+ do_etc_file(_PATH_APM_ETC_SUSPEND,NULL);
sync();
sleep(1);
ioctl(ctl_fd, APM_IOC_SUSPEND, 0);
@@ -481,7 +481,7 @@
void
stand_by(int ctl_fd)
{
- do_etc_file(_PATH_APM_ETC_STANDBY);
+ do_etc_file(_PATH_APM_ETC_STANDBY,NULL);
sync();
sleep(1);
ioctl(ctl_fd, APM_IOC_STANDBY, 0);
@@ -495,7 +495,8 @@
const char *fname = apmdev;
int ctl_fd, sock_fd, ch, suspends, standbys, resumes;
int statonly = 0;
- int powerstatus = 0, powerbak = 0, powerchange = 0;
+ int powerstatus = 0, powerstatus_old = 0;
+ int batstatus = 0, batstatus_old = 0;
int noacsleep = 0;
struct timespec ts = {TIMO, 0}, sts = {0, 0};
struct apm_power_info pinfo;
@@ -636,11 +637,8 @@
apmtimeout = 0;
/* wakeup for timeout: take status */
- powerbak = power_status(ctl_fd, 0, &pinfo);
- if (powerstatus != powerbak) {
- powerstatus = powerbak;
- powerchange = 1;
- }
+ powerstatus_old = power_status(ctl_fd, 0, &pinfo);
+ batstatus_old = pinfo.battery_life;
}
if (!rv)
@@ -653,10 +651,13 @@
APM_EVENT_INDEX(ev->data));
switch (APM_EVENT_TYPE(ev->data)) {
+ case APM_BATTERY_LOW:
+ batstatus_old = pinfo.battery_life;
+ break;
case APM_SUSPEND_REQ:
case APM_USER_SUSPEND_REQ:
case APM_CRIT_SUSPEND_REQ:
- case APM_BATTERY_LOW:
+ batstatus_old = pinfo.battery_life;
suspends++;
break;
case APM_USER_STANDBY_REQ:
@@ -671,19 +672,13 @@
case APM_NORMAL_RESUME:
case APM_CRIT_RESUME:
case APM_SYS_STANDBY_RESUME:
- powerbak = power_status(ctl_fd, 0, &pinfo);
- if (powerstatus != powerbak) {
- powerstatus = powerbak;
- powerchange = 1;
- }
+ powerstatus_old = power_status(ctl_fd, 0,
&pinfo);
+ batstatus_old = pinfo.battery_life;
resumes++;
break;
case APM_POWER_CHANGE:
- powerbak = power_status(ctl_fd, 0, &pinfo);
- if (powerstatus != powerbak) {
- powerstatus = powerbak;
- powerchange = 1;
- }
+ powerstatus_old = power_status(ctl_fd, 0,
&pinfo);
+ batstatus_old = pinfo.battery_life;
break;
default:
;
@@ -697,17 +692,15 @@
else if (standbys)
stand_by(ctl_fd);
else if (resumes) {
- do_etc_file(_PATH_APM_ETC_RESUME);
+ do_etc_file(_PATH_APM_ETC_RESUME,NULL);
syslog(LOG_NOTICE,
"system resumed from APM sleep");
}
- if (powerchange) {
- if (powerstatus)
- do_etc_file(_PATH_APM_ETC_POWERUP);
- else
- do_etc_file(_PATH_APM_ETC_POWERDOWN);
- powerchange = 0;
+ if (powerstatus != powerstatus_old || batstatus !=
batstatus_old) {
+ powerstatus = powerstatus_old;
+ batstatus = batstatus_old;
+ do_etc_file(_PATH_APM_ETC_POWERCHG,&pinfo);
}
} else if (ev->ident == sock_fd)
@@ -739,11 +732,14 @@
}
void
-do_etc_file(const char *file)
+do_etc_file(const char *file, struct apm_power_info *pinfo)
{
pid_t pid;
int status;
const char *prog;
+ char b_state[2];
+ char b_life[4];
+ char ac_state[2];
/* If file doesn't exist, do nothing. */
if (access(file, X_OK|R_OK)) {
@@ -764,7 +760,13 @@
return;
case 0:
/* We are the child. */
- execl(file, prog, (char *)NULL);
+ if (pinfo){
+ snprintf(b_state, sizeof(b_state), "%d",
pinfo->battery_state);
+ snprintf(b_life, sizeof(b_life), "%d",
pinfo->battery_life);
+ snprintf(ac_state, sizeof(ac_state), "%d",
pinfo->ac_state);
+ }
+ execl(file, prog, b_state, b_life, ac_state,
+ (char *)NULL);
_exit(1);
/* NOTREACHED */
default:
Index: pathnames.h
===================================================================
RCS file: /cvs/src/usr.sbin/apmd/pathnames.h,v
retrieving revision 1.5
diff -u -r1.5 pathnames.h
--- pathnames.h 14 Jun 2005 15:18:53 -0000 1.5
+++ pathnames.h 1 Dec 2010 21:55:38 -0000
@@ -35,6 +35,5 @@
#define _PATH_APM_ETC_SUSPEND _PATH_APM_ETC_DIR"/suspend"
#define _PATH_APM_ETC_STANDBY _PATH_APM_ETC_DIR"/standby"
#define _PATH_APM_ETC_RESUME _PATH_APM_ETC_DIR"/resume"
-#define _PATH_APM_ETC_POWERUP _PATH_APM_ETC_DIR"/powerup"
-#define _PATH_APM_ETC_POWERDOWN _PATH_APM_ETC_DIR"/powerdown"
+#define _PATH_APM_ETC_POWERCHG _PATH_APM_ETC_DIR"/powerchange"
#define _PATH_APM_NORMAL "/dev/apm"
And here is my powerchange script as an example:
#!/bin/sh
BAT_STATE=$1
BAT_LIFE=$2
AC_STATE=$3
SOUND1="O6L64AP64..CP64......AP64..A"
SOUND2="O6L64AP64..A"
if [ $AC_STATE -eq 1 ]; then
sysctl hw.setperf=100
fi
if [ $AC_STATE -eq 0 -a $BAT_LIFE -le 50 ]; then
sysctl hw.setperf=50
fi
if [ $AC_STATE -eq 0 -a $BAT_LIFE -eq 50 ]; then
# tell half battery if no AC power
echo "AC off and battery at $BAT_LIFE%." | logger -t APM
echo $SOUND2 > /dev/speaker
fi
case $BAT_STATE in
0)
# high
if [ $AC_STATE -eq 1 -a $BAT_LIFE -eq 100 ]; then
echo "Battery is fully charged." | logger -t APM
echo $SOUND2 > /dev/speaker
fi
;;
1)
# low
;;
2)
# critical
if [ $AC_STATE -eq 0 -a $BAT_LIFE -le 3 ]; then
echo "AC off and battery at $BAT_LIFE% !!!" | logger -t
APM
echo $SOUND1 > /dev/speaker
fi
if [ $AC_STATE -eq 0 -a $BAT_LIFE -le 1 ]; then
echo "Setting hw.setperf=0" | logger -t APM
sysctl hw.setperf=0
echo $SOUND1 > /dev/speaker
fi
;;
4)
# abscent
;;
esac