#! /usr/bin/perl -w # Waider 26/09/2000 # 30/09/2002 Modified to use /proc/apm. # 11/08/2003 Add ACPI support package Monitor::APM; use Monitor; @ISA = "Monitor"; sub init { my $mon = shift; # Do basic init $mon->SUPER::init(); # stash as a bunch of references $mon->{'starttime'} = time; $mon->{'lasttime'} = time; $mon->{'timeleft'} = 0; $mon->{'start'} = -1; $mon->{'last'} = -1; $mon->{'online'} = 0; $mon; } sub newdata { my $mon = shift; my $text = $mon->{'text'}; my $apm = ""; my @tips; print STDERR "Updating...\n"; # XXX could probably open and rewind repeatedly, rather than open/close if ( open( APM, "/proc/apm" )) { $apm = ; close( APM ); } elsif ( -d "/proc/acpi/battery" ) { $apm = acpi_to_apm( $mon ); push @tips, "ACPI"; } else { $text = "APM not\navailable"; } chomp( $apm ); # From the kernel 2.4 sources (arch/i386/kernel/apm.c): my ( $drvver, $biosver, $flags, $acstat, $btstat, $btflag, $btpercent, $bttime, $bttime_unit ) = split( / /, $apm ); $btpercent ||= "-1"; $btflag ||= "0xff"; $btstat ||= "0xff"; $acstat ||= "0xff"; $btpercent = "??" if $btpercent == -1; # GUH. $flags = eval( $flags ); $acstat = eval( $acstat ); $btstat = eval( $btstat ); $btflag = eval( $btflag ); if ( defined( $mon->{'DEBUG'}) && $mon->{'DEBUG'} ) { print "$apm\n"; print "Driver version: $drvver BIOS version: $biosver\n"; printf ( "Flags: %02x\n", $flags ); printf ( "A/C Status: %02x\n", $acstat ); printf ( "Battery Status: %02x\n", $btstat ); printf ( "Battery Flags: %02x\n", $btflag ); } $text = "$btpercent\n"; # Useful things: # flags: # 0x1 16bit APM, 0x2 32bit APM, 0x4 IDLE slows clock, 0x8, BIOS disabled, 0x10 BIOS disengaged # acstat: # 0x0 offline, 0x1 online, 0x2 backup power, 0xff unknown # btstat: # 0x0 high, 0x1 low, 0x2 critical, 0x3 charging, 0x4 selected batt not present, 0xff unknown # btflag: # 0x1 high, 0x2 low, 0x4 critical, 0x8 charging, 0x40 no sys batt, 0xff unknown # btpercent/bttime: -1 => unknown # bttime_unit: # min or sec # do this with a hash! if ( $flags & 0x1 ) { push @tips, "16-bit APM"; } if ( $flags & 0x2 ) { push @tips, "32-bit APM"; } if ( $flags & 0x4 ) { push @tips, "IDLE slows clock"; } if ( $flags & 0x8 ) { push @tips, "BIOS diabled"; } if ( $flags & 0x10 ) { push @tips, "BIOS disengaged"; } # Grab state my $starttime = $mon->{'starttime'}; my $lasttime = $mon->{'lasttime'}; my $timeleft = $mon->{'timeleft'}; my $start = $mon->{'start'}; my $last = $mon->{'last'}; my $online = $mon->{'online'}; # Are we plugged in or not? if ( $acstat & 0x01 ) { if ( !$online ) { $online = 1; $start = $btpercent; $last = -1; } } else { if ( $online ) { $start = $btpercent; $last = $btpercent; $starttime = time; $lasttime = time; $online = 0; } } my $warn = ""; if ( $btstat == 0x02 or $btflag & 0x02 ) { $warn = " (LOW)"; } $text = "$btpercent$warn\n"; # dummy, to catch the first run through $start = $btpercent if ( $start == -1 ); $last = $start if ( $last == - 1 ); # see if battery percentage has decreased if ( $last > $btpercent ) { $timeleft = $btpercent * ( time - $starttime ) / ( $start - $btpercent ); $last = $btpercent; } # now screw all that if the BIOS actually gave us usable time if ( $bttime != -1 ) { if ( $bttime_unit eq "min" ) { $bttime *= 60; } $timeleft = $bttime; } # and format that if ( !$online ) { $text .= sprintf( "%02d:%02d:%02d", (int( $timeleft / 3600 )), int( $timeleft / 60 ) % 60, $timeleft % 60 ); } else { if ( $btstat == 0xff && $btflag == 0xff ) { $text .= "mains(?)"; } else { if ( $btstat == 0x03 or $btflag & 0x8 ) { $text .= "charging"; } else { $text .= "mains"; } } } # Store state $mon->{'starttime'} = $starttime; $mon->{'lasttime'} = $lasttime; $mon->{'timeleft'} = $timeleft; $mon->{'start'} = $start; $mon->{'last'} = $last; $mon->{'online'} = $online; if ( @tips ) { $mon->set_tooltip( join( ", ", @tips )); } $text; } sub acpi_to_apm { my $mon = shift; # Convert the info in /proc/acpi/battery/* into an APM string. Loses info, but screw that! my $apm; my ( $drvver, $biosver, $flags, $acstat, $btstat, $btflag, $btpercent, $bttime, $bttime_unit ) = ( "1.4", "1.1", 0, 0, 0, 0, -1, -1, "?" ); # here's the output for a battery: # present: yes # design capacity: 54719 mWh # last full capacity: 53913 mWh # battery technology: rechargeable # design voltage: 14399 mV # design capacity warning: 5391 mWh # design capacity low: 3235 mWh # capacity granularity 1: 2 mWh # capacity granularity 2: 2 mWh # model number: Primary # serial number: 1FA50011 # battery type: LIon # OEM info: COMPAQ # present: yes # capacity state: ok # charging state: unknown # present rate: 0 mW # remaining capacity: 52530 mWh # present voltage: 16875 mV # # when not on mains, charging state => discharging and present rate => rate of discharge # get the ac adapter state for acstat if ( opendir( DIR, "/proc/acpi/ac_adapter" )) { for my $dir ( grep !/^\.\.?$/, readdir( DIR )) { if ( open( ACPI, "/proc/acpi/ac_adapter/$dir/state" )) { my $state = ; print STDERR "AC $dir $state" if $mon->{'DEBUG'}; $acstat |= 0x1 if $state =~ /on-line/; close( ACPI ); } else { print STDERR "Failed to get AC $dir state: $!\n" if $mon->{'DEBUG'}; } } } opendir( DIR, "/proc/acpi/battery" ); my @batteries = grep !/^\.\.?$/, readdir( DIR ); closedir( DIR ); my ( $max, $lev, $low, $crit, $rate ) = ( 0, 0, 0, 0, 0 ); for my $battery ( @batteries ) { open( BATT, "/proc/acpi/battery/$battery/info" ); my @bits = ; next unless $bits[0] =~ /yes/; close( BATT ); open( BATT, "/proc/acpi/battery/$battery/state" ); push @bits, ; close( BATT ); if ( $mon->{'DEBUG'} || 0 ) { print STDERR "Battery $battery\n"; print STDERR join( "", @bits ); print STDERR "\n"; } for my $bits ( @bits, @bits ) { # stupidity! chomp( $bits ); my ( $field, $value ) = split( /:\s*/, $bits, 2 ); $value =~ s/\s+$//; if ( $field eq "last full capacity" ) { #"design capacity" ) { ( $max ) = $value =~ /(\d+)/; } elsif ( $field eq "remaining capacity" ) { ( $lev ) = $value =~ /(\d+)/; } elsif ( $field eq "design capacity warning" ) { ( $low ) = $value =~ /(\d+)/; } elsif ( $field eq "design capacity low" ) { ( $crit ) = $value =~ /(\d+)/; } elsif ( $field eq "charging state" ) { if ( $value eq "unknown" ) { $btstat = 0xff; $btflag = 0xff; } elsif ( $value eq "discharging" ) { if ( $lev ) { if ( $lev > $low ) { $btflag = 0x00; $btstat |= 0x1; } elsif ( $lev > $crit ) { $btflag = 0x01; $btstat |= 0x2; } else { $btflag = 0x02; $btstat |= 0x4; } } } elsif ( $value eq "charging" ) { $btstat = 0x03; $btflag |= 0x8; $acstat |= 0x1; # xxx check power_resource } elsif ( $value eq "charged" ) { $btstat = 0x00; $btflag |= 0x1; $acstat |= 0x1; } } elsif ( $field eq "present rate" ) { ( $rate ) = $value =~ /(\d+)/; } } last; # XXX } $btpercent = sprintf( "%02d%%", $lev / $max * 100 ) if ( $max ); if ( $rate ) { $bttime_unit = "min"; $bttime = ( $lev - $crit ) / $rate * 60; } $apm = sprintf( "%s %s 0x%02x 0x%02x 0x%02x 0x%02x %s %d %s", $drvver, $biosver, $flags, $acstat, $btstat, $btflag, $btpercent, $bttime, $bttime_unit ); print STDERR "APM: $apm\n" if $mon->{'DEBUG'}; $apm; } 1;