Macのバッテリー情報を取得・記録してGoogleChartAPIでグラフ化


## はじめに
Qiita初投稿です。（普段ブログなので・・・）
何書くか迷ったのですが、自分の周りにMac使いが多いのでMac関連で最近自分が作ったものについて書こうかと思います。

Mac book（というかノートPC全般）を使っていて一番気になるのはやっぱりバッテリーの状態です。
今回、そのバッテリーの状態をスクリプトで定期的に取得し、GoogleChartAPIを使ってグラフ化するようにしたのでその方法を書きたいと思います。

同じようなことをしてくれるアプリはあるのですが、個人的にインストールするアプリは最小限にしたい派なので、バッテリーの状態を確認するためだけにアプリを入れるのは正直少し気が進まず。。
それなら作っちゃえ！と思って作りました。（もちろん既存のアプリ自体は素晴らしいものだと思いますので、入れるのも全然ありだと思います！）

結構スクリプト組んで同じようなことをしている方がいたので、参考にさせてもらいつつやったら割と簡単に出来ました。

## 必須環境
今回の記事の内容を行うにあたって必要な環境は以下の通りです。

- rubyのインストール: 今回作成するスクリプトはRubyで書くので。
- ローカルでのWebサーバ（Apache）: API叩くときにHttpRequestを使うので。MAMPあるならそれでやったほうが早いかもです。

## Macのバッテリー情報を取得
まずはMacのバッテリー情報を取得してみます。
Macのシステムレポートを見ると色々と書いてあるんですが、ターミナルで以下のコマンドを実行すると数値データで見ることが出来ます。

```bash
$ ioreg -l | grep Capacity
```

実行結果は以下のようになります。

```bash:実行結果
$ ioreg -l | grep Capacity
    | |    "MaxCapacity" = 5757
    | |    "CurrentCapacity" = 2995
    | |    "LegacyBatteryInfo" = {"Amperage"=3548,"Flags"=7,"Capacity"=5757,"Current"=2995,"Voltage"=12160,"Cycle Count"=188}
    | |    "DesignCapacity" = 6330
```

この中から、今回使うデータは以下の4つです。

- MaxCapacity: 完全充電時の容量（ｍAh）
- CurrentCapacity: 現在のバッテリー容量（mAh）
- Cycle Count: 充放電回数
- DesignCapacity: 設計時のバッテリー容量（mAh）

これらの数値データをスクリプトで定期的に取得し、グラフ化していきます。
なお、グラフ化するのは数値に変動がないDesignCapacityの値を除いた3つの値です。
DesignCapacityの値はあとで使うので覚えておきましょう。

## スクリプトの作成
さて、スクリプトを作っていきます。
作成する際には[こちらのサイト](http://pplog.org/?p=1270)が非常に参考になりました。
rubyで書いてますが、複雑なことはしていません。上記のioregコマンドの実行結果を元に文字列処理をして数値を取得し、putsするだけです。

putsする際にはそれぞれ日付（時刻）と数値をカンマ区切りでputsしていますが、これはグラフ化するときに扱いやすいようCSV形式にするためです。
CurrentCapacityは、後に1時間に1回取得するようにするために時刻までputsしています。他は1日1回取得するようにするので日付のみです。

### MaxCapacityの取得

```ruby:battery_max_log.rb
#!/usr/bin/ruby

require 'date'

# 現在の日付を取得し、フォーマット変換して文字列出力
now = Date.today.strftime('%Y/%m/%d')
capacity = `/usr/sbin/ioreg -l | grep MaxCapacity`.chomp.split(" = ")[1]
 
puts now+","+capacity
```

### CurrentCapacityの取得

```ruby:battery_current_log.rb
#!/usr/bin/ruby

# 現在時刻取得
now = Time.now.strftime('%Y/%m/%d %H:%M')
capacity = `/usr/sbin/ioreg -l | grep CurrentCapacity`.chomp.split(" = ")[1]

puts now+','+capacity
```

### Cycle Countの取得

```ruby:battery_cycle_log.rb
#!/usr/bin/ruby

require 'date'

# 現在の日付を取得し、フォーマット変換して文字列出力
now = Date.today.strftime('%Y/%m/%d')
cyclecount = `/usr/sbin/ioreg -l | grep CycleCount | grep -v Design`.chomp.split(" = ")[1]

puts now+","+cyclecount
```

## launchdで定期的にスクリプトを実行させる
さて、次に作ったスクリプトを定期的に実行させ、ログを残すようにします。
今回は、Mac（OS X）においてデーモン等の管理を行うlaunchdでデーモンを設定し、定期的に実行させたいと思います。
launchdについては長くなるので今回は触れませんが、簡単な説明は以前私がブログで記事にしてますので、[こちら](http://furudate.hatenablog.com/entry/2014/11/09/155017)からどうぞ。

今回は ```/Library/LaunchDaemons``` に以下の3つのファイルを作成します。
MaxCapacityとCycle Countは毎日0時に、CurrentCapacityは毎時スクリプトを実行し、ログに記録するようにしています。

### MaxCapacityの設定ファイル

```xml:maxBattery.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>maxBattery</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/ruby</string>
        <string>/Users/username/battery_max_log.rb</string>
    </array>
    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key>
        <integer>0</integer>
        <key>Minute</key>
        <integer>0</integer>
    </dict>
    <key>StandardOutPath</key>
    <string>/Users/username/max_battery.txt</string>
</dict>
</plist>
```

### CurretCapacityの設定ファイル

```xml:currentBattery.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>currentBattery</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/ruby</string>
        <string>/Users/username/battery_current_log.rb</string>
    </array>
    <key>StartCalendarInterval</key>
    <dict>
        <key>Minute</key>
        <integer>0</integer>
    </dict>
    <key>StandardOutPath</key>
    <string>/Users/username/current_battery.txt</string>
</dict>
</plist>
```

### Cycle Countの設定ファイル

```xml:cycleBattery.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>cycleBattery</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/ruby</string>
        <string>/Users/username/battery_cycle_log.rb</string>
    </array>
    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key>
        <integer>0</integer>
        <key>Minute</key>
        <integer>0</integer>
    </dict>
    <key>StandardOutPath</key>
    <string>/Users/username/cycle_battery.txt</string>
</dict>
</plist>
```

作成したらそれぞれのplistファイルを launchctl load コマンドでlaunchdに登録します。
これで定期的に実行され、ログを残すようになりました。

ちなみにログデータはこんな感じになると思います。

```text:current_battery.txt
2014/12/04 21:00,2854
2014/12/04 22:00,1414
2014/12/04 23:00,3035
2014/12/05 00:00,5459
2014/12/05 01:00,4820
2014/12/05 02:00,3554
```

あとはこのログデータを使ってグラフ化するだけです。

## グラフ化しよう
GoogleChartAPIを使ってグラフ化します。
GoogleChartAPIとはGoogleが提供しているグラフ作成サービスで、かなり簡単に自分のページ上にグラフを作成することが出来ます。グラフの種類も多めなので、色々と使いどころはあるかと思います。

このAPIはJavaScriptで叩くので、JavaScriptで実装していきます。

### まずはhtmlの作成
とりあえずグラフを表示するページを作ります。
自分の場合は3つのボタンを作成し、対応するボタンを押したらそれぞれのグラフが出るようにしました。

```html:index.html
<!DOCTYPE html>
<html>
    <head>
        <script src="./js/jquery-1.7.1.min.js"></script>
        <script type="text/javascript" src="https://www.google.com/jsapi"></script>
        <script type="text/javascript">
            // Visualization API と折れ線グラフ用のパッケージのロード
            google.load("visualization", "1", {packages:["corechart"]});
        </script>
        <script type="text/javascript" src="./js/index.js"></script>
    </head>
    <body>
        <div id="btn_area">
            <input type="button" value="バッテリー残量" id="current_battery_btn" />
            <input type="button" value="最大バッテリー容量" id="max_battery_btn" />
            <input type="button" value="充放電回数" id="cycle_battery_btn" />
        </div>
        <!-- グラフを描く div 要素 -->
        <div id="chart_div" style="width: 80%; height: 400px;"></div>
    </body>
</html>
```

いらないところは省きました。
また、JavaScriptの記述はindex.jsに書くようにしているので読み込ませています。

#### 重要な点

- jqueryを読み込む（必須ではないですがjqueryで書いたほうが便利です）
- [https://www.google.com/jsapi](https://www.google.com/jsapi)の読み込み
- Visualization API と折れ線グラフ用のパッケージのロード

### JavaScriptでグラフ作成
さて、いよいよグラフ作成のメインとなる部分です。

#### 処理の流れ
このソースは以下のような処理の流れになります。

1. ボタンが押されたら対応するログデータを読み込みcsv3Arrayメソッドでグラフ用の配列を作成（縦軸を数値に、横軸を日付にする）
2. CurrentCapacityとMaxCapacityのときは縦軸を%表記に変換
3. drawChartメソッドでグラフの種類やオプションを決定し、データを元としたグラフを作成する
4. 描画されたグラフを設定しCallBack関数を呼び出す

#### ソースコード
ソースコードは以下の通りです。
少し長いですが、基本的にはソース内のコメントに書いてあるとおりです。

```javascript:index.js
$(function() {
    // バッテリー残量ボタンが押された場合
    $("#current_battery_btn").click(function(){
        // データ入力
        var data = csv3Array("./current_battery.txt", 0);
        var mdata = csv3Array("./max_battery.txt", 1);
        // 数値データを%値に
        data = currentDataPercent(data, mdata);
        // Google Visualization API ロード時のコールバック関数の設定と呼び出し
        google.setOnLoadCallback(drawChart(data, 0));
    });
    // 最大バッテリー容量ボタンが押された場合
    $("#max_battery_btn").click(function(){
        // MaxCapacity data 作成
        var maxData = csv3Array("./max_battery.txt", 1);
        // 一回だけ実行
        if (maxData[1][1] > 100){
            // 最大バッテリー容量の場合は出荷時最大値との%値（小数点第2位まで）に変換
			// 6330はDesignCapacityの値です。環境によって違うと思うので適宜変更してください。
            for(var i = 1; i < maxData.length; i++){
                maxData[i][1] = maxData[i][1] / 6330 * 100 * 100;
                maxData[i][1] = Math.round(maxData[i][1]) / 100;
            }
        }
        google.setOnLoadCallback(drawChart(maxData, 1));
    });
    // 充放電回数ボタンが押された場合
    $("#cycle_battery_btn").click(function(){
        var cycleData = csv3Array("./cycle_battery.txt", 2);
        // Google Visualization API ロード時のコールバック関数の設定と呼び出し
        google.setOnLoadCallback(drawChart(cycleData, 2));
    });
});

// CSV出力関数
// flg 0 バッテリー残量
// flg 1 バッテリー最大値
// flg 2 充放電回数
function csv3Array(filePath, flg) {
    var csvData = [];
    var data = new XMLHttpRequest();

    if (flg === 0){
        csvData = [["時刻", "バッテリー残量"]];
    }else if (flg === 1){
        csvData = [["日付", "バッテリー最大値"]];
    }else{
        csvData = [["日付", "充放電回数"]];
    }

    data.open("GET", filePath, false); //true:非同期,false:同期
    data.send(null);

    var LF = String.fromCharCode(10); //改行ｺｰﾄﾞ
    var lines = data.responseText.split(LF);

    for (var i = 0; i < lines.length;++i) {
       var cells = lines[i].split(",");
       if( cells.length != 1 ) {
          csvData.push(cells);
        }
    }
    // 日付をDate型にし、実データを数値に変換
    for (var j = 1; j < csvData.length; j++){
        csvData[j][0] = new Date(csvData[j][0]);
        csvData[j][1] = Number(csvData[j][1]);
    }
    return csvData;
}

// CurrentCapacityのデータを%にする
// その日のMaxCapacityの値を取得して、その値を元に%表記にする（日毎にMaxの値が変わることがあるため）
function currentDataPercent(data, mdata){
    for (var i = 1; i < data.length; i++){
        // dataの月日を取得
		// getMonthでは0-11月になってしまうので、+1する
        var currentMonth = data[i][0].getMonth() + 1;
        var currentDate = data[i][0].getDate();

        for (var j = 1; j < mdata.length; j++){
            // maxDataの月日を取得
            var maxMonth = mdata[j][0].getMonth() + 1;
            var maxDate = mdata[j][0].getDate();
            // 二つの月日が一致しているか
            if (currentMonth === maxMonth && currentDate === maxDate){
                data[i][1] = Math.round(data[i][1] / mdata[j][1] * 100);
                break;
            }
        }
    }
    return data;
}

// グラフ作成関数
// flg 0 バッテリー残量
// flg 1 バッテリー最大値
// flg 2 充放電回数
function drawChart(data, flg){
    var title;
    var chartType;
    var vAxisTitle;
    var max;
    var min = new Date();
    var format;
   
    if (flg === 0){
        title = "バッテリー残量";
        chartType = 'AreaChart';
        vAxisTitle = "%";
        max = 100;
		// 最小値を現在から2週間前に（見やすさ考慮）
        min.setDate(min.getDate() - 14);
        format = "yyyy/MM/dd HH:mm"
    }else if (flg === 1){
        title = "最大バッテリー容量";
        chartType = 'LineChart';
        vAxisTitle = "%";
        max = 100;
        min = null;
        format = "yyyy/MM/dd"
    }else{
        title = "充放電回数";
        chartType = 'LineChart';
        vAxisTitle = "回";
        max = null;
        min = null;
        format = "yyyy/MM/dd"
    }

    var wrapper = new google.visualization.ChartWrapper({
        chartType: chartType,
        dataTable: data,
        options: {
            title: title,
            legend: {
                position: 'none'
            },
            hAxis: {
                format: format,
                viewWindow: {
                    min:min, 
                }
            },
            vAxis: {
                title: vAxisTitle,
                viewWindow:{
                    max: max,
                }
            }
        },
        containerId: 'chart_div'
    });
    wrapper.draw();
}
```

drawChartメソッドでグラフの種類やオプションを定義しています。
今回はCurrentCapacityのグラフをAreaChartに、その他のグラフをLineChartにしましたが、他にどんな種類やオプションがあるのかは、[こちらのサイト](http://yohshiy.blog.fc2.com/blog-entry-195.html)を参考にしてください。

#### 実際に作成されるグラフ
実際に作成されるグラフはこんな感じになります。（ちなみにボタンはCSSで見やすくしています）
うーん、最大バッテリー容量が着実に減ってきてますね・・・

![current.png](https://qiita-image-store.s3.amazonaws.com/0/28484/6cfeeddc-6dd9-6ac1-bd3f-a1027025ab16.png "current.png")


![max.png](https://qiita-image-store.s3.amazonaws.com/0/28484/32d14b7b-18a3-c229-352c-c54fe87f7f44.png "max.png")

![cycle.png](https://qiita-image-store.s3.amazonaws.com/0/28484/4bfe74ed-a829-4dbb-f39e-35f484ff3f86.png "cycle.png")


## さいごに
以上でMacのバッテリー状態をグラフ化することが出来ました。
GoogleChartAPIのおかげで非常に簡単にグラフ化出来たので良かったです。すごい。さすがGoogle。

私は結構このグラフを毎回見てしまいます。結構気にする性格なので・・・（ちなみに私が使っているAndroidにもバッテリー状態をグラフ化するアプリを入れています笑）
特に満充電をしすぎてないかと最大バッテリー容量を確認しています。

やっぱりノートPCもスマホもバッテリーを長持ちさせるというのは結構大事なことだと思うので、みなさんも気を使っていきましょう！
ちなみに[こちらのサイト](http://yohoho.jp/12159)にMacのバッテリーを長持ちさせるコツがまとめられてます。非常に参考になります！

何か間違い等ありましたらコメント下さい。


## 参考にさせて頂いたサイト
[Macのバッテリー状態を取得する(2) - mac日記](http://d.hatena.ne.jp/yao3/20090811/1249999999)

[バッテリー情報を記録するrubyスクリプトをcronで実行 &laquo; pplog.org](http://pplog.org/?p=1270)

[Google Chart Tools の使い方 | プログラマーズ雑記帳](http://yohshiy.blog.fc2.com/blog-entry-195.html)