掌控Apple Silicon MacBook电池健康的神器

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

手动安装

  1. GitHub Releases 下载最新版本
  2. 解压到系统目录:
bash 复制代码
sudo tar -xzf batt-*.tar.gz -C /usr/local/bin
  1. 安装守护进程:
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 电池寿命得到有效延长。

相关推荐
nancy_princess16 小时前
clip实验
人工智能·深度学习
飞哥数智坊17 小时前
TRAE Friends@济南第4次活动:100+极客集结,2小时极限编程燃爆全场!
人工智能
AI自动化工坊17 小时前
ProofShot实战:给AI编码助手添加可视化验证,提升前端开发效率3倍
人工智能·ai·开源·github
飞哥数智坊17 小时前
一场直播涨粉 2 万的背后!OpenClaw + 飞书,正在重塑软件交付的方式
人工智能
飞哥数智坊17 小时前
养虾记第3期:安装、调教、落地,这场沙龙我们全聊了
人工智能
再不会python就不礼貌了17 小时前
从工具到个人助理——AI Agent的原理、演进与安全风险
人工智能·安全·ai·大模型·transformer·ai编程
AI医影跨模态组学17 小时前
Radiother Oncol 空军军医大学西京医院等团队:基于纵向CT的亚区域放射组学列线图预测食管鳞状细胞癌根治性放化疗后局部无复发生存期
人工智能·深度学习·医学影像·影像组学
A尘埃17 小时前
神经网络的激活函数+损失函数
人工智能·深度学习·神经网络·激活函数
没有不重的名么18 小时前
Pytorch深度学习快速入门教程
人工智能·pytorch·深度学习
有为少年18 小时前
告别“唯语料论”:用合成抽象数据为大模型开智
人工智能·深度学习·神经网络·算法·机器学习·大模型·预训练