最終更新日:2014/02/18

AutoInflowDisable - MacBook Pro 15 early 2011のバッテリーの充電をソフトウェア制御でオン・オフする

MacBook Pro 15 early 2011のバッテリーを据え置き状態で付けっぱなしにしておいたら1年半でバッテリーがダメになって交換しました(月一回の充放電はしてましたが)。今度はちゃんと対策しようとした記録です。

前回書いたChargeInhibitは自分の勘違いでDisableInflowが実行されています。これはAC電源を切る命令です。とりあえず目的が達成できていたのであまり詳しく調べませんでしたが、昨日やたら人が来たので久しぶりに調べてみたら根本的な勘違いをしていました!

環境

MacBook Pro 15 early 2011 @ 10.6.8

DisableInflowを実行するコード

DisableInflowはIOPMAssertionCreateWithNameで実行できます。


// Disables AC Power Inflow (requires root to initiate)
#define kIOPMAssertionTypeDisableInflow         CFSTR("DisableInflow")
#define kIOPMInflowDisableAssertion             kIOPMAssertionTypeDisableInflow
IOPMAssertionID assertionID = 0;
IOReturn ret = IOPMAssertionCreateWithName(kIOPMInflowDisableAssertion,
                    kIOPMAssertionLevelOn,
                     CFSTR("kIOPMInflowDisableAssertion"),
                    &assertionID);

参考リンク

やり方

上のリンクのsendSmartBatteryCommandは完璧なコードで間違いはありません。

動かない原因は引数が10.6.8では変わっているのと、実行プロセスにroot権限が無いとエラーになります。

引数

ハードやOS依存の部分があると思うので試す方は要注意ですがMacBook Pro 15 early 2011 @ 10.6.8では次の命令でオン・オフできます。

kSBUCInflowDisable = 0,

なので下記コードでACをソフトウェアで接続、切断できます。

sendSmartBatteryCommand(0, 1); //充電オフ

sendSmartBatteryCommand(0, 0); //充電オン

root権限が必要

この命令を送るプロセスにはroot権限がないと命令が実行されません。

今はターミナルでsudoを付けて動かしています。

資料&発見までの流れ

sendSmartBatteryCommandは下のAppleSmartBatteryManagerUserClient::secureChargeInhibit関数を実行する関数です。AppleSmartBatteryManagerUserClient::externalMethod経由で実行されます。

参考リンク先では

(kSBUCChargeInhibit, 255)

で実行しています。

enum {
    kSBUCInflowDisable = 0,
    kSBUCChargeInhibit = 1
};

なので、(1,255)です。

しかし、2006のAppleSmartBatteryManagerUserClientのソースは次のようになっています。


IOReturn AppleSmartBatteryManagerUserClient::secureInflowDisable(
    int level,
    int *return_code)
{
    int             admin_priv = 0;
    IOReturn        ret = kIOReturnNotPrivileged;

    if( !(level == 0 || level == 1))
    {
        *return_code = kIOReturnBadArgument;
        return kIOReturnSuccess;
    }

    ret = clientHasPrivilege(fOwningTask, kIOClientPrivilegeAdministrator);
    admin_priv = (kIOReturnSuccess == ret);

    if(admin_priv && fOwner) {
        *return_code = fOwner->disableInflow( level );
        return kIOReturnSuccess;
    } else {
        *return_code = kIOReturnNotPrivileged;
        return kIOReturnSuccess;
    }

}
enum {
    kSBInflowDisable = 0,
    kSBChargeInhibit = 1,
    kSBSetPollingInterval = 2,
    kSBSMBusReadWriteWord = 3
};

これでわかったのは、sendSmartBatteryCommandの返り値kretは常にkIOReturnSuccessでuc_returnに実際のエラーが格納されているということです。

そこでどのエラーが返ってきているか確認したら次の2つが返ってきていました

#define kIOReturnNotPrivileged   iokit_common_err(0x2c1) // privilege violation 
#define kIOReturnBadArgument     iokit_common_err(0x2c2) // invalid argument 

(0,255)で実行した時は0x2c2kIOReturnBadArgumentが返ってきています。上のAppleSmartBatteryManagerUserClient::secureChargeInhibitのソースに書いてある通り、0、1で指定すると動きました。

kSBInflowDisableは0なので0ですが、kSBChargeInhibitらしき1はやっぱり指定しても駄目なのでChargeInhibitは今のところやっぱり駄目です…。

rootが必要なのはIOPMLibPrivate.hのヘッダにより予想していたので常時sudoで実行していましたが、sudo無しで実行するとkIOReturnNotPrivilegedのエラーが返ってきました。

// Disables AC Power Inflow (requires root to initiate)
#define kIOPMAssertionTypeDisableInflow         CFSTR("DisableInflow")
#define kIOPMInflowDisableAssertion             kIOPMAssertionTypeDisableInflow

// Disables battery charging (requires root to initiate)
#define kIOPMAssertionTypeInhibitCharging       CFSTR("ChargeInhibit")
#define kIOPMChargeInhibitAssertion             kIOPMAssertionTypeInhibitCharging

実行上の注意

実行テストメモ

バッテリー制御ソフト - AutoInflowDisable

過充電状態にならないように自動で制御するアプリケーションです。

機能

使い方

  1. 実行上の注意をよく読んで下さい。
  2. 自分の環境が実行テストメモにあるか確認してください。無ければ人柱になる覚悟で。
  3. 使ったらどうなるかAutoInflowDisable使用記録もよく読んで下さい。
  4. AppleScriptエディタで開いて、propertyで指定されている設定を好きなようにします。デフォルトでは通知で動作状況が表示されますが、慣れてきて必要ないならtheIsDontShowNotificationを1にすることでオフにすることもできます。
  5. アプリケーションをrootで起動させます;:同梱のAutoInflowDisableRunRoot.appを実行するとパスワード入力画面が表示され、入力するとrootで起動します。起動項目にAutoInflowDisableRunRoot.appを登録しておいて下さい。
  6. InflowDisable状態でOSを終了した時は、次回起動時に必ずこのアプリケーションを起動して充電が開始されるようにしてください
  7. 実行して動いたり、動かなかったりしたのを掲示板に環境を書き込んでくださると実行テストメモなどに追加しておきます。
  8. rootで実行した時のログはsystem.logで見れます。
  9. 動かない場合startCharging«event ASBMExec» {0, 0}stopCharging«event ASBMExec» {0, 1}の値をいろいろ弄るといけるかもしれません。ただし、何が起こるかは分からないので自己責任でやってください。この値がsendSmartBatteryCommandにそのまま渡されます。エラーメッセージは文字列か16進数の文字列で返されますので参考にして下さい。

AutoInflowDisable使用記録

MacBook Pro 15 early 2011 @ 10.6.8 @ wifi使用 @ DisplayPortにディスプレイ、USBハブ、USBディスプレイアダプタ、マウス、Firewire 800のHDDが常時繋がっています
3週間ぐらい使っているが、ちゃんと40%、80%でバッテリの充電開始、停止が行われている。
「完全充電時の容量(mAh):6850ぐらい」でwifiやUSBディスプレイアダプタを使っているのもあるのか、だいたい1時間程度で80%から40%になります。40%から80%になるのも1時間ぐらいです。その為、バッテリーの充放電回数は順調に?増えていく一方なのでこれでいいのかちょっと不安なところもあります。
スリープ時に充電を停止しているが、スリープ中にバッテリーが0になってMacが終了してしまうケースは一回もないです。16時間ぐらいのスリープだと最低の40%でも大丈夫なようです。
2014/01/23 スリープ時に完全放電してしまう
いつものように帰宅してMacBook Proを開くと完全放電したようで、ディープスリープ状態になっていました。バッテリーは無くなっていますが、作業状態は保持されていました。ただ、完全放電がバッテリーにダメージを与えたようで「完全充電時の容量(mAh): 5356」に一気に減ってしまいました…。
数時間放置して戻ってきたら「 完全充電時の容量(mAh): 6851」になってました!ほぼ元通りです。というか、完全放電前にちょうど記録してたのですがその値が「完全充電時の容量(mAh): 6847」なのでちょっと増えました!時々、放電させたほうがバッテリーいいというのはこういうことなのでしょうか。
2014/01/24 充電したままスリープしたが完全充電量が結構減った!?
昨日、完全放電したので充電量が少ない時は充電したままにするようにしたのですが、その為、今日はスリープ時に充電したままになっていました。で、帰宅してちょっと使った時に確認すると100%から94%になった時に「完全充電時の容量(mAh): 6697」と減っていました。しばらく放置したらまた回復するか確認したいです。駄目なら、充電量が少なくて充電させなくて完全放電、ディープスリープで運用したいと思います。
2014/01/25 完全充電量回復せず?
今現在、80%ですが 「完全充電時の容量(mAh): 6656」と6800台から落ちたままです…。放電より過充電のほうがダメージあるのかな?
2014/01/26
ログに「完全充電時の容量(mAh)」を記録するようにしたのですが、6737と6657あたりを行ったり来たりで、6800台は記録されていません。
2014/02/05
毎日6時間ぐらい付けっぱなしで使用していて前回から10日ぐらい経ちました。AutoInflowDisableの動作は完璧で特に不具合は無いようです。充電放電回数は一日2回ぐらい増えてます。容量は6723ぐらいです。充1年半ぐらいで放電回数1000回になりそうですが大丈夫でしょうか。mAhは10日で30減ったとすると、10日*6000/30=2000日ぐらい持つのでしょうか?
2014/02/15
いつものように使い続けて容量は6725です。前回より増えてますね!。ちなみに休みの日は起きてから寝るまでつけっぱなしです。充放電回数は9日で27回増えています。一日平均3回でしょうか。一年で1000回超えそうです。
2014/02/18
久しぶりにソースを眺めていじっているとAutoChargeInhibitがAutoInflowDisableだったという衝撃の事実を発見!

課題

ダウンロード

AutoChargeInhibitがダウンロードできます。