WSLでEmacsのbattery.elを使えるようにする

# 背景
paypay20%キャンペーンと18,000円キャシュバックに惹かれて
Surface pro 6 を購入し、Emacs環境をWSLで新たに構築しました
その際に、タイトルバーに表示していたバッテリー残量が出ていませんでした
携帯機なのでバッテリー表示は必須と思い対策を調査しました
![emacs-battery.png](https://qiita-image-store.s3.amazonaws.com/0/242567/91fbb5d6-1996-d584-2c41-77864261ec01.png)
# 結論
下記コードで解決

```lisp:battery-wsl.el
;; 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](https://github.com/fujimotok/emacs-battery-wsl-el)に用意しました
# 調査ログ
## バッテリー情報取得関数を探す
モードラインへの表示を制御するのに以下ように設定していました
`(setq battery-mode-line-format " %b%p%%")`
この`battery-mode-line-format`を`C-h v`で変数の`*Help*`を見てみると

```lisp:*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`のリンクからコードへジャンプすると

```lisp: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/"`となっており、その配下のファイル構成も異なっております

```bash:shell.log
$ 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`を参考にすると

```lisp:*Help*
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式が戻り値となります

```lisp:battery-wsl.el
(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