batt - Apple Silicon MacBook 电池充电控制器
batt 是一款专为 Apple Silicon MacBook 设计的电池充电控制工具,能够有效延长电池寿命。通过智能限制充电阈值,防止电池长期处于满电状态,从而减缓电池老化过程。
功能特性
- 智能充电限制:设置上下充电阈值(10%-100%),像 ThinkPad 一样保护电池健康
- MagSafe LED 控制:根据充电状态自动控制 MagSafe LED 指示灯颜色
- 睡眠保护机制:防止系统睡眠时电池过充,支持空闲睡眠和系统睡眠防护
- 电源适配器控制:可手动启用或禁用电源适配器供电
- 实时状态监控:提供详细的电池状态和电源信息监控
- 图形化界面:提供直观的 GUI 界面,方便用户操作
安装指南
系统要求
- macOS 11.0 或更高版本
- Apple Silicon MacBook
- 需要 root 权限进行系统级安装
快速安装
bash
# 使用自动安装脚本
curl -fsSL https://github.com/charlie0129/batt/raw/main/install.sh | bash
手动安装
- 从 GitHub Releases 下载最新版本
- 解压到系统目录:
bash
sudo tar -xzf batt-*.tar.gz -C /usr/local/bin
- 安装守护进程:
bash
sudo batt install --allow-non-root-access
Homebrew 安装
bash
brew install batt
sudo brew services start batt
使用说明
基础命令
设置充电上限为 80%:
bash
batt limit 80
禁用电池充电限制:
bash
batt disable
查看当前状态:
bash
batt status
高级功能
启用 MagSafe LED 控制:
bash
batt magsafe-led enable
防止空闲睡眠:
bash
batt prevent-idle-sleep enable
禁用充电前睡眠保护:
bash
batt disable-charging-pre-sleep enable
电源适配器控制
启用电源适配器:
bash
batt adapter enable
禁用电源适配器:
bash
batt adapter disable
核心代码
充电限制核心逻辑
go
// pkg/daemon/loop.go
func maintainLoopInner(force bool) {
maintainLoopInnerLock.Lock()
defer maintainLoopInnerLock.Unlock()
charge, err := smcConn.GetBatteryCharge()
if err != nil {
logrus.Errorf("failed to get battery charge: %v", err)
return
}
pluggedIn, err := smcConn.IsPluggedIn()
if err != nil {
logrus.Errorf("failed to check if plugged in: %v", err)
return
}
// 根据充电状态和配置决定是否充电
if pluggedIn && conf.UpperLimit() < 100 && charge >= conf.UpperLimit() {
// 达到上限,停止充电
if err := smcConn.DisableCharging(); err != nil {
logrus.Errorf("failed to disable charging: %v", err)
}
maintainedChargingInProgress = true
} else if pluggedIn && conf.UpperLimit() < 100 && charge <= conf.LowerLimit() {
// 低于下限,开始充电
if err := smcConn.EnableCharging(); err != nil {
logrus.Errorf("failed to enable charging: %v", err)
}
maintainedChargingInProgress = true
} else {
maintainedChargingInProgress = false
}
}
SMC 通信模块
go
// pkg/smc/apple_smc.go
type AppleSMC struct {
conn gosmc.Connection
capabilities map[string]bool
}
func (c *AppleSMC) EnableCharging() error {
logrus.Tracef("EnableCharging called")
// 预 Tahoe 固件版本
if c.capabilities[ChargingKey1] && c.capabilities[ChargingKey2] {
err := c.Write(ChargingKey1, []byte{0x0})
if err != nil {
return err
}
err = c.Write(ChargingKey2, []byte{0x0})
return err
}
// Tahoe 固件版本
return c.Write(ChargingKey3, []byte{0x00, 0x00, 0x00, 0x00})
}
func (c *AppleSMC) DisableCharging() error {
logrus.Tracef("DisableCharging called")
// 预 Tahoe 固件版本
if c.capabilities[ChargingKey1] && c.capabilities[ChargingKey2] {
err := c.Write(ChargingKey1, []byte{0x2})
if err != nil {
return err
}
err = c.Write(ChargingKey2, []byte{0x2})
return err
}
// Tahoe 固件版本
return c.Write(ChargingKey3, []byte{0x01, 0x00, 0x00, 0x00})
}
客户端 API 通信
go
// pkg/client/client.go
type Client struct {
socketPath string
httpClient *http.Client
}
func (c *Client) SetLimit(l int) (string, error) {
return c.Put("/limit", strconv.Itoa(l))
}
func (c *Client) GetCharging() (bool, error) {
ret, err := c.Get("/charging")
if err != nil {
return false, pkgerrors.Wrapf(err, "failed to get charging status")
}
return parseBoolResponse(ret)
}
func (c *Client) Send(method string, path string, data string) (string, error) {
logrus.WithFields(logrus.Fields{
"method": method,
"path": path,
"data": data,
"unix": c.socketPath,
}).Debug("sending request")
var resp *http.Response
var err error
url := "http://unix" + path
// 通过 Unix socket 与守护进程通信
switch method {
case "GET":
resp, err = c.httpClient.Get(url)
case "PUT":
req, err2 := http.NewRequest("PUT", url, strings.NewReader(data))
if err2 != nil {
return "", fmt.Errorf("failed to create request: %w", err2)
}
resp, err = c.httpClient.Do(req)
default:
return "", fmt.Errorf("unknown method: %s", method)
}
if err != nil {
return "", fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
return string(b), err
}
系统睡眠监听
go
// pkg/daemon/sleep_darwin.go
//export canSystemSleepCallback
func canSystemSleepCallback() {
logrus.Debugln("received kIOMessageCanSystemSleep notification")
if !conf.PreventIdleSleep() {
logrus.Debugln("PreventIdleSleep is disabled, allow idle sleep")
C.AllowPowerChange()
return
}
// 系统刚唤醒时拒绝空闲睡眠
if timeAfterWokenUp := time.Since(lastWakeTime);
timeAfterWokenUp < time.Duration(preSleepLoopDelaySeconds)*time.Second {
logrus.Debugf("system has just waked up, deny idle sleep")
C.CancelPowerChange()
return
}
// 立即运行循环更新充电状态
maintainLoopInner(false)
if maintainedChargingInProgress {
logrus.Debugln("maintained charging in progress, deny idle sleep")
C.CancelPowerChange()
} else {
C.AllowPowerChange()
}
}
batt 通过深度集成 macOS 系统服务,提供了完整的电池健康管理解决方案,让您的 Apple Silicon MacBook 电池寿命得到有效延长。