Linuxでバッテリー残量表示が食い違う

2025-05-09 device linux oss pc

一言でいうと、「だいたいバッテリーコントローラーのせい」、ということでした。

なぜそうなるのでしょうか。

何が起こっているのか

btop では 77% と低めの数値が表示され、同時に、 i3status-rs では 95% と表示され、高めの数値が表示され、残量が食い違ってしまいます。

※btop 1.4.2、i3status-rs 0.33.2、Linux 6.14.5-arch1-1, MacBook A1502の環境です。

どこから取得しているのか

どちらも、 /sys/class/power_supply/<DEVICE_NAME> 配下の、デバイスドライバが出力する、正規化済の値を参照していると考えました。

私の環境では、以下のファイル群が見えます。

$ find /sys/class/power_supply/BAT0/ -maxdepth 1 -type f
/sys/class/power_supply/BAT0/uevent
/sys/class/power_supply/BAT0/charge_full_design
/sys/class/power_supply/BAT0/technology
/sys/class/power_supply/BAT0/current_now
/sys/class/power_supply/BAT0/charge_now
/sys/class/power_supply/BAT0/present
/sys/class/power_supply/BAT0/manufacturer
/sys/class/power_supply/BAT0/type
/sys/class/power_supply/BAT0/charge_full
/sys/class/power_supply/BAT0/capacity
/sys/class/power_supply/BAT0/cycle_count
/sys/class/power_supply/BAT0/voltage_now
/sys/class/power_supply/BAT0/status
/sys/class/power_supply/BAT0/alarm
/sys/class/power_supply/BAT0/model_name
/sys/class/power_supply/BAT0/current_avg
/sys/class/power_supply/BAT0/temp
/sys/class/power_supply/BAT0/voltage_min_design

軽く計算してみる

  • charge_now / charge_full (現在容量基準)
    • i3status-rs と同じ (高い)
  • charge_now / charge_full_design (設計容量基準)
    • btop と同じ (低い)

ここに差が生じるのは、バッテリーが劣化しているからですね。

ソースコードを追い、btop の実装から charge_full_design を見つけられれば、「ユーザーに見せたい値が設計容量基準であることが要因」ということになります。お疲れ様でした。

とは行きませんでした。

btopのソースコードを追いましたが、charge_full_design は見つけられませんでした。

考えると、そもそも、バッテリーの設計容量を基準に、現在のバッテリー残量を見せたい、と思う人はそう多くないはずです。

では、なぜそうなるのか

i3status-rs では

i3status-rs のソースコード を追うと、取得方針は以下の順の様です。

  1. charge_now / charge_full
  2. energy_now / energy_full (今回存在せず)
  3. capacity 直読み
let capacity = calc_capacity(charge_now, charge_full)
    .or_else(|| calc_capacity(energy_now, energy_full))
    .or(capacity)
    .or_else(|| capacity_level.and_then(CapacityLevel::percentage))
    .error("Failed to get capacity")?;

私の環境では、charge_now / charge_full が採用されそうです。

btop では

btop のソースコード から追うと、取得方針は次の順の様です。

  1. capacity 直読み
  2. energy_now / energy_full (今回存在せず)
  3. charge_now / charge_full
//? Try to get battery percentage
if (percent < 0) {
    try {
        percent = stoll(readfile(b.base_dir / "capacity", "-1"));
    }
    catch (const std::invalid_argument&) { }
    catch (const std::out_of_range&) { }
}
if (b.use_energy_or_charge and percent < 0) {
    try {
        percent = round(100.0 * stoll(readfile(b.energy_now, "-1")) / stoll(readfile(b.energy_full, "1")));
    }
    catch (const std::invalid_argument&) { }
    catch (const std::out_of_range&) { }
}
if (b.use_energy_or_charge and percent < 0) {
    try {
        percent = round(100.0 * stoll(readfile(b.charge_now, "-1")) / stoll(readfile(b.charge_full, "1")));
    }
    catch (const std::invalid_argument&) { }
    catch (const std::out_of_range&) { }
}
if (percent < 0) {
    has_battery = false;
    return {0, 0, 0, ""};
}

私の環境では capacity 直読みが採用されていそうです。

これが解決の手がかりになりそうです。

capacity ってなんだろう

突然出てきた capacity ですが、実際に値を取得してみると、私の環境では設計容量基準のバッテリー残量を出力している様に見えます。

しかしなぜ、そのような実装なのでしょう。

ここの値を決めているのはデバイスドライバのはずです。

デバイスドライバがどの様に実装されるべきなのか気になってきたので、ここで、Linuxのマニュアルを読むことにしました。

Linux power supply classのドキュメント には、以下の様に記述されています。

CAPACITY attribute represents capacity in percents, from 0 to 100.

0〜100とはありますが、何を基準として100とするべきか、記載されていません。

Q&Aを読んでみると、まさに、私の求めていたことが書いてありました。 「ドライバで計算せず、バッテリーコントローラーくんの値を尊重するべき」と書かれています(超要約)。

Q: Suppose, my battery monitoring chip/firmware does not provides capacity in percents, but provides charge_{now,full,empty}. Should I calculate percentage capacity manually, inside the driver, and register CAPACITY attribute? The same question about time_to_empty/time_to_full. A: Most likely, no. This class is designed to export properties which are directly measurable by the specific hardware available.

Inferring not available properties using some heuristics or mathematical model is not subject of work for a battery driver. Such functionality should be factored out, and in fact, apm_power, the driver to serve legacy APM API on top of power supply class, uses a simple heuristic of approximating remaining battery capacity based on its charge, current, voltage and so on. But full-fledged battery model is likely not subject for kernel at all, as it would require floating point calculation to deal with things like differential equations and Kalman filters. This is better be handled by batteryd/libbattery, yet to be written.

〜完〜

つまり、「私のラップトップのバッテリーコントローラーが、設計容量基準の残量をよこしていて、非直感的である」ということになりますね。

バッテリーってむずかしいんだなぁ。