Posted 2007-11-17T20:39:00+01:00 in unix

Adding a battery status meter to ion 3

After running out of battery power on my notebook twice in a week, I decided I really needed some sort of battery status information on my screen. I didn't have that. Why not? Because I'm running the ion window manager on my Apple Powerbook. Call it whatever you like, but that certainly is an esoteric niche market, and it doesn't come with a battery status meter in the system tray. Well, there are other battery meters, but they only work for systems that support APM or ACPI. The Powerbooks have a special power management unit, the PMU, that have their own interface. Luckily, it's quite easy to write a simple text-based meter that can be added to the standard ion statusbar.

I made a usability little twist to the meter so that it stays out of my way when it has nothing useful to say: When the battery is charged and you're on AC power, you don't see anything. Only when the system is on AC power and charging the battery, you'll see a tilde sign, ~, followed by the percentage that the battery is charged. When the system is running on battery power, you'll see an equal, =, sign, followed by the remaining capacity in percentages.

As it happens, it's also my first stab at writing Lua code. It's a bit like javascript. I like it.

Obligatory example screenshot here. Look at the bottom, to the right of "Email". The system is currently charging and at 77% capacity.

Download the statusd_pmu.lua script here

Source code

-- A battery statusbar meter for the ion3 window manager for Apple Powerbooks
-- running Linux. These machines do not use APM or ACPI but have a PMU, Power
-- Management Unit.
--
-- Install the meter in ~/.ion3/statusd_pmu.lua
--
-- Edit your ~/.ion3/cfg_statusbar.lua to add this meter: 
--   template="%date - %pmu_battery %filler%systray",
--
-- See /usr/src/linux/include/pmu.h and
-- /usr/src/linux/drivers/macintosh/via-pmu.c for some documentation on the
-- exported data. 
--
-- Author: Jeroen Pulles
-- Rotterdam, 15 november 2007


local pmu_base_settings = {
  update_interval = 60*1000, -- every minute
  important_threshold = 30, -- 30% cap. remaining
  critical_threshold = 8, -- 8% capacity remaining
}

local pmu_settings = pmu_base_settings

local pmu_timer


-- Read the pmu battery info
local function read_pmu_battery_data ()
  -- assume only one of possible two batteries is present:
  local f = assert(io.open("/proc/pmu/battery_0", "r"))
  local data = f:read("*all")
  f:close()
  local _, _, charge = string.find(data, "[^_]charge%s*:%s*(%d+)")
  local _, _, max_charge = string.find(data, "max_charge%s*:%s*(%d+)")
  local _, _, amperage = string.find(data, "current%s*:%s*(-?[%d]+)")
  return tonumber(amperage), tonumber(charge), tonumber(max_charge)
end


-- Convert the battery data to usable info:
local function get_pmu_battery_state ()
  local amp, charge, max_charge = read_pmu_battery_data()
  local pct = (charge / max_charge) * 100
  if amp == 0 then 
    -- charge doesn't always go to exactly full max charge
    -- but the laptop stops charging anyway: 
    pct = 100
  end
  return amp, pct
end


-- Print the battery status to stdout, for debugging purposes:
local function print_batt_info ()
  local amp, pct = get_pmu_battery_state()
  local state = ""
  if amp > 0 then
    state = "Charging "
  elseif amp < 0 then
    state = "Remaining "
  end
  print(string.format("%s%d%%", state, pct))
end


-- Write the current state to the statusbar:
local function inform_pmu ()

  local amp, pct = get_pmu_battery_state()

  if amp == 0 then
    -- Do no show anything when on AC power and fully charged: 
    statusd.inform("pmu_battery", "")
  elseif amp > 0 then
    -- Charging the battery on AC power, percentage charged:
    statusd.inform("pmu_battery", string.format("~%d%%", pct))
  else
    -- On battery power, percentage remaining: 
    statusd.inform("pmu_battery", string.format("=%d%%", pct))
  end

  if pct <= pmu_settings.critical_threshold then
    statusd.inform("pmu_battery_hint", "critical")
  elseif pct <= pmu_settings.important_threshold then
    statusd.inform("pmu_battery_hint", "important")
  else
    statusd.inform("pmu_battery_hint", "normal")
  end

end


-- Statusbar update loop:
local function update_pmu ()
  inform_pmu()
  pmu_timer:set(pmu_settings.update_interval, update_pmu)
end


-- Run the script: 
if statusd then 
  -- we're a statusbar plugin:
  pmu_settings = table.join(statusd.get_config("pmu"), pmu_base_settings)
  pmu_timer = statusd.create_timer()
  update_pmu()
else
  -- run as standalone:
  print_batt_info()

end

-- vim: set ts=4 sw=4 expandtab