背景
paypay20%キャンペーンと18,000円キャシュバックに惹かれて
Surface pro 6 を購入し、Emacs環境をWSLで新たに構築しました
その際に、タイトルバーに表示していたバッテリー残量が出ていませんでした
携帯機なのでバッテリー表示は必須と思い対策を調査しました
結論
下記コードで解決
;; battery-modeの設定
(setq battery-status-function #'battery-linux-sysfs-wsl) ; バッテリー情報取得関数を独自のものに置き換える
;;(setq battery-update-interval 60) ; 必要があれば更新頻度設定[sec]
(display-battery-mode 1)
(setq battery-mode-line-format " %b%p%%")
;; タイトルバーに時計とバッテリー情報表示(こちらを使用するのであれば上記のbattery-modeの設定は不要)
(when (window-system)
;; display-timeより先にsetしておかないとdefaultの書式になる
(setq display-time-string-forms
'((format "%s/%s/%s" year month day)
(format "(%s:%s)" 24-hours minutes)))
;;(setq display-time-interval 1)
(display-time) ;; display-time-stringの有効化
;; タイトルバーの書式設定 global-mode-stringにdisplay-time-stringが入っている
;; バッファがファイルのときはフルパス、でなければバッファ名表示
;; if(buffer-file-name) の評価がsetq時で終わらないよう:eval
(setq battery-status-function #'battery-linux-sysfs-wsl)
;;(setq battery-update-interval 1)
(display-battery-mode 1)
(setq battery-mode-line-format " %b%p%%")
(setq frame-title-format '("" (:eval (if (buffer-file-name) " %f" " %b"))
" --- " global-mode-string) ) )
;; バッテリー取得関数本体(でかいのでinit-loaderで分割をオススメ)
(defun battery-linux-sysfs-wsl ()
"Get ACPI status information from Linux kernel.
This function works only with the Windows Subsystem for Linux.
The following %-sequences are provided:
%c Current capacity (mAh)
%r Current rate
%B Battery status (verbose)
%b Battery status (charging:'+' discharging:'')
%d Temperature (in degrees Celsius)
%p Battery load percentage
%L AC line status (verbose)
%m Remaining time (to charge or discharge) in minutes
%h Remaining time (to charge or discharge) in hours
%t Remaining time (to charge or discharge) in the form `h:min'"
(let (charging-state ac-state temperature hours
energy-now energy-now-rate power-now current-now voltage-now
(dir "/sys/class/power_supply/battery"))
(with-temp-buffer
(erase-buffer)
(ignore-errors (insert-file-contents
(expand-file-name "capacity" dir)))
(setq energy-now-rate (or (thing-at-point 'number) "N/A"))
(erase-buffer)
(ignore-errors (insert-file-contents
(expand-file-name "status" dir)))
(setq charging-state (or (thing-at-point 'word) "N/A"))
(erase-buffer)
(ignore-errors (insert-file-contents
(expand-file-name "temp" dir)))
(setq temperature (or (thing-at-point 'number) "N/A"))
(setq temperature (if (numberp temperature) (* temperature 0.1)))
(erase-buffer)
(ignore-errors (insert-file-contents
(expand-file-name "charge_counter" dir)))
(setq energy-now (or (thing-at-point 'number) "N/A"))
(erase-buffer)
(ignore-errors (insert-file-contents
(expand-file-name "current_now" dir)))
(setq current-now (or (thing-at-point 'number) "N/A"))
(unless (or (stringp energy-now) (stringp current-now)
(stringp energy-now-rate) (zerop current-now))
(if (string= charging-state "Discharging")
(setq hours (/ energy-now current-now))
(setq hours (/ (* energy-now (- 100.0 energy-now-rate))
energy-now-rate current-now ))))
(erase-buffer)
(ignore-errors (insert-file-contents
(expand-file-name "voltage_now" dir)))
(setq voltage-now (or (thing-at-point 'number) "N/A"))
(setq power-now (if (and (numberp current-now) (numberp voltage-now))
(* (/ current-now 1000.0) (/ voltage-now 1000000.0))))
;; current-now[mA]->[A] voltage-now[uV]->[V]
(erase-buffer)
(setq dir "/sys/class/power_supply/ac")
(ignore-errors (insert-file-contents
(expand-file-name "online" dir)))
(setq ac-state (cond ((eq (thing-at-point 'number) 1) "AC")
((eq (thing-at-point 'number) 0) "BAT")
(t "N/A")))
)
;; set return value
(list (cons ?c (number-to-string energy-now))
(cons ?r (if hours (number-to-string power-now) "N/A"))
(cons ?B charging-state)
(cons ?b (if (string= charging-state "Charging") "+" ""))
(cons ?d (number-to-string temperature))
(cons ?p (number-to-string energy-now-rate))
(cons ?L ac-state)
(cons ?m (if hours (format "%d" (* hours 60)) "N/A"))
(cons ?h (if hours (format "%d" hours) "N/A"))
(cons ?t (if hours (format "%d:%02d" hours (* (- hours (floor hours)) 60)) "N/A")))
)
)
一応、関数本体だけの.elをこちら#githubに用意しました
調査ログ
バッテリー情報取得関数を探す
モードラインへの表示を制御するのに以下ように設定していました
(setq battery-mode-line-format " %b%p%%")
このbattery-mode-line-format
をC-h v
で変数の*Help*
を見てみると
battery-mode-line-format is a variable defined in ‘battery.el’.
Its value is " %b%p%%"
Original value was nil
Documentation:
Control string formatting the string to display in the mode line.
Ordinary characters in the control string are printed as-is, while
conversion specifications introduced by a ‘%’ character in the control
string are substituted as defined by the current value of the variable
‘battery-status-function’. Here are the ones generally available:
%c Current capacity (mAh or mWh)
%r Current rate of charge or discharge
%B Battery status (verbose)
%b Battery status: empty means high, ‘-’ means low,
‘!’ means critical, and ‘+’ means charging
%d Temperature (in degrees Celsius)
%L AC line status (verbose)
%p Battery load percentage
%m Remaining time (to charge or discharge) in minutes
%h Remaining time (to charge or discharge) in hours
%t Remaining time (to charge or discharge) in the form ‘h:min’
You can customize this variable.
battery-status-function
変数の値によって%*
が差し替えられるとあります
というわけで、battery-status-function
変数に入っている関数が
バッテリー情報を取得していると推測されます
現在の値は*Help*
内のbattery-status-function
のリンクをRETすると
battery-status-function is a variable defined in ‘battery.el’.
Its value is nil
This variable may be risky if used as a file-local variable.
Documentation:
Function for getting battery status information.
The function has to return an alist of conversion definitions.
Its cons cells are of the form
(CONVERSION . REPLACEMENT-TEXT)
CONVERSION is the character code of a "conversion specification"
introduced by a ‘%’ character in a control string.
You can customize this variable.
nil
となっております
*Help*
内のbattery.el
のリンクからコードへジャンプすると
(defcustom battery-status-function
(cond ((and (eq system-type 'gnu/linux)
(file-readable-p "/proc/apm"))
#'battery-linux-proc-apm)
((and (eq system-type 'gnu/linux)
(file-directory-p "/proc/acpi/battery"))
#'battery-linux-proc-acpi)
((and (eq system-type 'gnu/linux)
(file-directory-p "/sys/class/power_supply/")
(directory-files "/sys/class/power_supply/" nil
battery--linux-sysfs-regexp))
#'battery-linux-sysfs)
((and (eq system-type 'berkeley-unix)
(file-executable-p "/usr/sbin/apm"))
#'battery-bsd-apm)
((and (eq system-type 'darwin)
(condition-case nil
(with-temp-buffer
(and (eq (call-process "pmset" nil t nil "-g" "ps") 0)
(> (buffer-size) 0)))
(error nil)))
#'battery-pmset)
((fboundp 'w32-battery-status)
#'w32-battery-status))
"Function for getting battery status information.
The function has to return an alist of conversion definitions.
Its cons cells are of the form
(CONVERSION . REPLACEMENT-TEXT)
CONVERSION is the character code of a \"conversion specification\"
introduced by a `%' character in a control string."
:type '(choice (const nil) function)
:group 'battery)
とあり、環境によって
- battery-linux-proc-apm
- battery-linux-proc-acpi
- battery-linux-sysfs
- battery-bsd-apm
- battery-pmset
- w32-battery-status
がセットされるようです
このなかでWSL環境に近いのは、Pathが"/sys/class/power_supply/"
でセットされる
battery-linux-sysfs
なのでこの関数が目的の関数となります
バッテリー情報が取れない原因
WSLでは一般のlinux環境とは異なるpathでバッテリー情報を提供しているためでした
battery-linux-sysfs
では"/sys/class/power_supply/bat0/"
を期待しているのですが
WSL環境では、"/sys/class/power_supply/battery/"
となっており、その配下のファイル構成も異なっております
$ ls /sys/class/power_supply/
ac battery usb
$ ls /sys/class/power_supply/battery/
capacity charge_counter current_now health present status technology temp type voltage_now
$ cat /sys/class/power_supply/battery/capacity
64 # 残量[%]
$ cat /sys/class/power_supply/battery/charge_counter
3959 # 充電容量[mAh]
$ cat /sys/class/power_supply/battery/current_now
705 # 電流[mA]
$ cat /sys/class/power_supply/battery/health
Good # 劣化状態
$ cat /sys/class/power_supply/battery/present
1 # 電力供給状態?
$ cat /sys/class/power_supply/battery/status
Discharging # 充電状態(非充電中)
$ cat /sys/class/power_supply/battery/status
Charging # 充電状態(充電中)
$ cat /sys/class/power_supply/battery/status
Full # 充電状態(充電完了)
$ cat /sys/class/power_supply/battery/technology
Li-ion # 製品仕様
$ cat /sys/class/power_supply/battery/temp
258 # 温度 摂氏x10 ?
$ cat /sys/class/power_supply/battery/type
Battery # 電力供給仕様
$ cat /sys/class/power_supply/battery/voltage_now
8115000 # 電圧 [uV] => 8.115[V] ?
WSL用のバッテリー情報取得関数の設計
バッテリー情報取得関数が返す必要がある情報は
battery-linux-sysfs
を参考にすると
battery-linux-sysfs is a compiled Lisp function in ‘battery.el’.
(battery-linux-sysfs)
Get ACPI status information from Linux kernel.
This function works only with the new ‘/sys/class/power_supply/’
format introduced in Linux version 2.4.25.
The following %-sequences are provided:
%c Current capacity (mAh or mWh)
%r Current rate
%B Battery status (verbose)
%d Temperature (in degrees Celsius)
%p Battery load percentage
%L AC line status (verbose)
%m Remaining time (to charge or discharge) in minutes
%h Remaining time (to charge or discharge) in hours
%t Remaining time (to charge or discharge) in the form ‘h:min’
%c Current capacity (mAh or mWh)
charge_counter
の値を返したら良さそう
%r Current rate
current_now
とvoltate_now
の積で電力[w]を計算したらいけそう
%B Battery status (verbose)
battery/status
の値を返したら良さそう
%d Temperature (in degrees Celsius)
temp
の値を返したら良さそう
%p Battery load percentage
capacity
の値を返したら良さそう
%L AC line status (verbose)
ac/status
の値を返したら良さそう
%m Remaining time (to charge or discharge) in minutes
%h Remaining time (to charge or discharge) in hours
%t Remaining time (to charge or discharge) in the form `h:min'"
2つのアルゴリズムが必要
残り稼働時間(非充電時)
残り稼働時間[h] = 電池残容量[Wh] / 消費電力[Wh] = 電池容量[Ah] / 消費電力[Ah]
電池容量[Ah] = charge_counter[mAh] * 1000
消費電力[Ah] = current_now[mA] * 1000 * 1[h]
残り充電時間(充電時)
残り充電時間[h] = (電池満容量[Wh] - 電池残容量[Wh]) / 供給電力[Wh]
電池満容量[Wh]が情報としてないので
電池満容量[Wh] * 電池残量[%] = 電池残容量[Wh]
電池満容量[Wh] = 電池残容量[Wh] / 電池残量[%]
(電池満容量[Wh] - 電池残容量[Wh]) = 電池残容量[Wh] / 電池残量[%] - 電池残量[%]
= 電池残容量[Wh] * (1 - 電池残量[%]) / 電池残量[%]
= V[V] * charge_counter[mAh] * 1000 * (1 - capacity) / capacity
残り充電時間[h] = V[V] * charge_counter[mAh] * 1000 * (1 - capacity) / capacity / (V[V] * current_now[mA] * 1000 * 1[h])
= charge_counter[mAh] * (1 - capacity) / capacity / current_now[mA]
WSL用のバッテリー情報取得関数の実装
前章で述べた情報を
(list (cons ?c val) (cons ?r val) ...)
の形で返す関数を実装します
lispなので最後に評価されたS式が戻り値となります
(let (charging-state ac-state temperature hours
energy-now energy-now-rate power-now current-now voltage-now
(dir "/sys/class/power_supply/battery"))
(with-temp-buffer ;; このブロック限りのバッファを生成しcurrent_bufferとする
(erase-buffer) ;; バッファに読み込む前にバッファを空に
(ignore-errors (insert-file-contents
(expand-file-name "capacity" dir)))
(setq energy-now-rate (or (thing-at-point 'number) "N/A"))
(erase-buffer)
(ignore-errors (insert-file-contents
(expand-file-name "status" dir)))
(setq charging-state (or (thing-at-point 'word) "N/A"))
(erase-buffer)
(ignore-errors (insert-file-contents
(expand-file-name "temp" dir)))
(setq temperature (or (thing-at-point 'number) "N/A"))
(setq temperature (if (numberp temperature) (* temperature 0.1)))
(erase-buffer)
(ignore-errors (insert-file-contents
(expand-file-name "charge_counter" dir)))
(setq energy-now (or (thing-at-point 'number) "N/A"))
(erase-buffer)
(ignore-errors (insert-file-contents
(expand-file-name "current_now" dir)))
(setq current-now (or (thing-at-point 'number) "N/A"))
(unless (or (stringp energy-now) (stringp current-now)
(stringp energy-now-rate) (zerop current-now))
;; 変数がN/Aのときパス。充電完了となるとcurrent-now=0。zero-dev回避のため0のときパス
(if (string= charging-state "Discharging")
(setq hours (/ energy-now current-now))
(setq hours (/ (* energy-now (- 100.0 energy-now-rate))
energy-now-rate current-now ))))
(erase-buffer)
(ignore-errors (insert-file-contents
(expand-file-name "voltage_now" dir)))
(setq voltage-now (or (thing-at-point 'number) "N/A"))
(setq power-now (if (and (numberp current-now) (numberp voltage-now))
(* (/ current-now 1000.0) (/ voltage-now 1000000.0))))
;; current-now[mA]->[A] voltage-now[uV]->[V]
(erase-buffer)
(setq dir "/sys/class/power_supply/ac") ;; acはpathが変わるので変更
(ignore-errors (insert-file-contents
(expand-file-name "online" dir)))
(setq ac-state (cond ((eq (thing-at-point 'number) 1) "AC")
((eq (thing-at-point 'number) 0) "BAT")
(t "N/A")))
)
;; 戻り値(リスト型)を設定
(list (cons ?c energy-now)
(cons ?r (if hours power-now "N/A"))
(cons ?B charging-state)
(cons ?d temperature)
(cons ?p energy-now-rate)
(cons ?L ac-state)
(cons ?m (if hours (format "%d" (* hours 60)) "N/A"))
(cons ?h (if hours (format "%d" hours) "N/A"))
(cons ?t (if hours (format "%d:%02d" hours (* (- hours (floor hours)) 60)) "N/A")))
)
こいつをdefun
で囲えば完成です
最後に
今回は、実装までの過程を詳細に書いてみました
Emacsにはeldoc
や*Help*
が用意されている上に
ソースコードにも直接アクセスできるのですが
その活かし方がイマイチ広まってないと思います
*Help*
が使いこなせるとEmacsの拡張がもっと楽しめるので
皆さん使って見ましょう
- C-h v 変数(variable)のhelp
- C-h f 関数(function)のhelp
- C-h b キーマップ(bind)のhelp