awenforlinux

不跳就锁——用心率广播实现人走锁屏

  •  
  •   awenforlinux · 12h 0m ago · 4696 views
    ## 背景:人走必须锁屏,但我总会忘

    🐶公司办公区有比较严格的安全规范:离开工位必须锁屏。道理都懂,可实际执行起来总是磕磕绊绊——去洗手间、接水、开会,经常起身就走了,屏幕还亮着。
    ![clip_1784758448046_n4br1c.png]( https://file.awen.me/images/clip_1784758448046_n4br1c.png)
    理想中的人走锁屏应该满足几个条件:

    - **无感**:不需要我手动操作,也不需要我佩戴额外硬件。
    - **及时**:人离开工位后几秒内就要锁屏。
    - **不误判**:坐在电脑前正常工作时不应该突然锁屏。
    - **隐私**:不要把我的生物特征上传到云端。

    我手里正好有一块支持心率广播的智能手表,每天戴着它跑步和监测睡眠。既然它已经 24 小时戴在手上,能不能让它充当"人体存在传感器"?答案是可以。

    ## 核心思路:用心率广播 + 信号强度判断"人还在不在"

    BLE ( Bluetooth Low Energy )心率设备会广播标准心率服务 `0x180D`,其中的心率测量特征 `0x2A37` 每秒推送一次当前心率。只要手表在 Mac 的蓝牙覆盖范围内,Mac 就能收到稳定的心率数据。

    当我的身体离开 Mac 一段距离后:

    1. 心率广播中断,Mac 超过一定时间收不到心率。
    2. 即使还在广播,信号强度 RSSI 也会显著下降。

    于是判定逻辑很简单:

    - **心率超时**:超过 10 秒没收到心率,认为设备失联,锁屏。
    - **弱信号**:连续 3 秒 RSSI 低于 -75 dBm ,认为人已经走远,锁屏。

    这两种条件互为补充:前者应对手表被取下或蓝牙关闭,后者应对人走远但广播仍在的情况。

    ## 项目结构

    我把应用拆成了三个部分:

    ```
    HeartRateLockWidget/
    ├── HeartRateLock/ # macOS 状态栏后台 App
    │ ├── HeartRateLockApp.swift # 应用入口、状态栏菜单、设置窗口
    │ ├── BLEHeartRateManager.swift # CoreBluetooth 扫描/绑定/连接/心率订阅
    │ └── LockScreenManager.swift # 触发 macOS 锁屏
    ├── HeartRateWidget/ # 桌面/侧边小组件
    │ ├── HeartRateWidget.swift # 实时心率 Widget
    │ ├── HeartRateCurveWidget.swift # 心率曲线 Widget
    │ └── HeartRateWidgetBundle.swift # Widget 入口
    └── HeartRateLockShared/ # App 与 Widget 共享代码
    └── SharedConfig.swift # App Group 、UserDefaults Key 、设置项
    ```

    `HeartRateLock` 是一个没有 Dock 图标的状态栏应用,启动后躲在菜单栏里持续扫描和监听心率。`HeartRateWidget` 是 WidgetKit 扩展,负责把心率展示在桌面或通知中心。

    ## 关键实现

    ### 1. 扫描与绑定设备

    macOS 的 `CoreBluetooth` 扫描时不指定服务 UUID ,这样可以列出周围所有 BLE 广播设备。用户从菜单栏的「选择设备」里按信号强度排序找到手表,点击完成绑定。绑定信息会持久化,下次启动自动重连。

    ```swift
    private let heartRateServiceUUID = CBUUID(string: "0x180D")
    private let heartRateMeasurementUUID = CBUUID(string: "0x2A37")

    func centralManager(
    _ central: CBCentralManager,
    didDiscover peripheral: CBPeripheral,
    advertisementData: [String: Any],
    rssi RSSI: NSNumber
    ) {
    // 收集设备名、RSSI ,按信号强度排序展示
    }
    ```

    这里有一个坑:macOS 出于隐私保护,不会把 BLE 设备的 MAC 地址暴露给 App ,只会给一个随机生成的 `CBPeripheral.identifier`。所以无法通过 MAC 地址过滤,只能让用户手动选择。另外很多手表在广播里不暴露名字,首次绑定可能显示为「未知设备」,连接成功后我会再读取 GAP 服务 `0x2A00` 里的真实设备名来校正。

    ### 2. 解析心率数据

    心率测量特征的通知数据格式是 BLE 标准:

    ```
    byte 0: flags
    bit 0: 心率值是 uint8 还是 uint16
    byte 1: 心率值( uint8 )
    byte 1-2: 心率值( uint16 )
    ```

    解析代码:

    ```swift
    private func parseHeartRateMeasurement(_ data: Data) -> Int? {
    guard !data.isEmpty else { return nil }
    let flags = data[0]
    let isUInt16 = (flags & 0x01) != 0
    if isUInt16, data.count >= 3 {
    return Int(data[1]) | (Int(data[2]) << 8)
    } else if data.count >= 2 {
    return Int(data[1])
    }
    return nil
    }
    ```

    ### 3. 锁屏触发

    每次收到心率或读到 RSSI 时,都会更新 `lastAliveAt`。每秒检查一次:

    ```swift
    private func checkTimeout() {
    guard let reference = lastAliveAt ?? connectAttemptAt else { return }
    if Date().timeIntervalSince(reference) > SharedConfig.timeoutSeconds, !didLockForTimeout {
    didLockForTimeout = true
    LockScreenManager.lockScreen()
    }
    }
    ```

    弱信号判定类似:连续低于阈值秒数达到设定值就锁屏,避免 RSSI 抖动导致误锁。

    ### 4. 触发 macOS 锁屏

    最早我用的是 `/System/Library/CoreServices/Menu Extras/User.menu/Contents/Resources/CGSession -suspend`,但新版 macOS 已经移除了这个路径。为了兼容不同系统版本,我做了三层兜底:

    1. 调用 `login.framework` 的私有接口 `SACLockScreenImmediate`。
    2. 如果找不到,再尝试 `CGSession -suspend`。
    3. 如果都不行,最后用 `pmset displaysleepnow` 熄屏,配合系统设置里的「唤醒时需要密码」实现等效锁屏。

    ```swift
    enum LockScreenManager {
    static func lockScreen() {
    if lockViaPrivateFramework() { return }
    if FileManager.default.fileExists(atPath: lockCommand) {
    // CGSession -suspend
    return
    }
    // pmset displaysleepnow 兜底
    }
    }
    ```

    ### 5. 小组件与 App 共享数据

    Widget 扩展和主 App 属于不同进程,不能直接共享内存。我用了一个小技巧:主 App (非沙盒)直接写 Widget 容器内的 plist 文件,Widget 读取自己的容器。这样比 App Group 的 `UserDefaults(suiteName:)` 更稳定,也绕过了 macOS AppData TCC 策略对 Widget 读取的限制。

    ```swift
    public enum SharedStore {
    private static let plistURL: URL = {
    if isSandboxed {
    // Widget:自己容器内的 Application Support
    } else {
    // 主 App:写 Widget 容器内的同一个文件
    }
    }()
    }
    ```

    Widget 每秒请求一次时间线刷新,实际刷新频率由系统调度决定,所以主 App 负责实时性,Widget 只是显示层。

    ## 还加了哪些功能

    ### 空闲锁屏兜底

    万一哪天没戴手表,应用还内置了键鼠空闲检测。通过 `CGEventSource.secondsSinceLastEventType` 读取键盘、鼠标最后一次活动时间,超过设定秒数也锁屏。这个兜底还可以设置只在指定时间段生效,比如只在工作时间开启。

    ### 开机自启

    用 `SMAppService.mainApp.register()` 注册登录项,实现开机自动启动,不需要在「系统设置 > 通用 > 登录项」里手动添加。

    ### 设置面板

    状态栏点击「设置…」可以调整:

    - 信号弱锁屏阈值(-90 ~ -40 dBm )
    - 连续弱信号秒数
    - 心率超时秒数
    - 键鼠空闲锁屏秒数及时段
    - 开机自启开关

    ## 实际使用效果

    戴上手表坐在工位,状态栏会显示实时心率。起身去洗手间,走出两三米后 RSSI 开始下降,大约 3 秒后屏幕自动锁定。回来后解锁电脑,手表会重新连上,继续显示心率。

    误触发的情况极少,因为同时满足「信号持续弱」和「心率中断」才会锁屏。坐在椅子上正常打字时,即使偶尔 RSSI 波动一下,也不会触发锁屏。

    ## 踩过的坑

    1. **macOS 不暴露 MAC 地址**:无法自动识别手表,必须手动绑定一次。
    2. **部分华为手表不广播标准心率服务**:它们只走华为运动健康私有协议,这种手表用不了。
    3. **CGSession 被移除**:新版系统锁屏 API 变了,需要多层兜底。
    4. **App Group 共享存储被 TCC 拦截**:主 App 写 App Group 的 UserDefaults 后,Widget 读不到,最后改用直接写 Widget 容器 plist 。
    5. **沙盒权限**:锁屏需要辅助功能权限,首次运行要在系统设置里允许。
    58 replies    2026-07-27 20:35:17 +08:00
    Lockroach
        1
    Lockroach  
       11h 52m ago   ❤️ 3
    把检测能力对接开放出来,可以做一个末日摇篮系统了😄
    Siriusie
        2
    Siriusie  
       11h 43m ago
    @Lockroach 执剑人是吧(
    JerryCat
        3
    JerryCat  
       11h 22m ago   ❤️ 1
    allinQQQ
        4
    allinQQQ  
       11h 15m ago
    既然还是蓝牙,就用蓝牙信号强度就可以了,没必要搞那么复杂,有类似产品我用过
    remarrexxar
        5
    remarrexxar  
       11h 6m ago
    快捷指令的自动化里配置,当 mac 和 airpods 断开连接时,锁定屏幕
    340746
        6
    340746  
       11h 6m ago
    心率广播会增加手表耗电吧
    ijse
        7
    ijse  
       10h 58m ago   ❤️ 21
    @allinQQQ 这个能做到 万一死在了工位上,也会自动锁屏,只用蓝牙检测做不到这一点
    94
        8
    94  
       10h 55m ago
    Win 都有动态锁的功能,Mac 没有提供类似的功能吗?

    Rache1
        9
    Rache1  
       10h 54m ago
    人走锁屏都已经成了肌肉记忆了,Ctrl+CMD+Q ,一键的事儿。
    iv2ex
        10
    iv2ex  
       10h 52m ago
    是不是做一个监测 鼠标+键盘 活跃情况的也行?工作时间坐在电脑前,鼠标和键盘总有一个是活跃的吧。
    huangmingyou
        11
    huangmingyou  
       10h 51m ago
    简单的有用蓝牙检测的.我现在是绑定了一个 F11, 顺手一点就锁,习惯就好.
    300
        12
    300  
       10h 48m ago
    自动 push 全部项目 [:doge]
    Takashi123
        13
    Takashi123  
       10h 46m ago   ❤️ 9
    解决了死在工位时电脑不会锁屏的痛点。
    nc
        14
    nc  
       10h 46m ago
    心脏骤停后自动 git commit git push
    baizhu
        15
    baizhu  
       10h 39m ago
    这个走最好是那个走
    JDog
        16
    JDog  
       10h 38m ago
    @94 有~ Apple Watch 支持,不晓得其它设备支不支持
    shalou8023
        17
    shalou8023  
       10h 38m ago   ❤️ 2
    @ijse 都死在工位上了,还管他锁不锁屏。。。
    wsyzzz
        18
    wsyzzz  
       10h 37m ago
    鼠标上绑了一个一键锁屏宏就行了,按一下就锁屏很方便
    peasant
        19
    peasant  
       10h 36m ago
    刚看到标题的时候我以为是那个走了,还在想人都走了就锁屏吗,怎么不清空数据
    yanqing07
        20
    yanqing07  
       10h 30m ago
    @shalou8023 你这是要笑死我吗,赔钱
    chulei
        21
    chulei  
       10h 13m ago
    支持支持
    AutumnVerse
        22
    AutumnVerse  
       10h 9m ago via iPhone
    肌肉记忆+1
    起身的同时就按下锁屏键了
    AllenZ0
        23
    AllenZ0  
       10h 8m ago
    一个触发角锁屏,手指划一下的事儿。
    shalou8023
        24
    shalou8023  
       10h 7m ago
    @peasant 原来你说的是这个走了,我以为是我想到那个走了
    cemian
        25
    cemian  
       10h 5m ago
    我都是将 mac 设置为鼠标滑到右下角锁屏,走的时候一划就完事了。
    rich1e
        26
    rich1e  
       9h 50m ago
    op 麻烦你组织一下内容格式、没有看的心情 @awenforlinux
    Vegetable
        27
    Vegetable  
       9h 48m ago
    这不是很奇怪吗?为什么非得用广播这种费电的信号?说脱裤子放屁对不起这个点子的可玩性,但是如果只是为了检测锁屏,那完全没有必要用心率。
    finull
        28
    finull  
       9h 46m ago
    BLEUnlock 我也在用,不过就是周围设备太多了,先找个偏僻的地方设置好设备
    ostrichb
        29
    ostrichb  
       9h 39m ago
    AI Slop 內文
    sangbiao
        30
    sangbiao  
       9h 32m ago
    挺好,猝死了直接锁屏,像夜神月的抽屉
    cvooc
        31
    cvooc  
       9h 29m ago   ❤️ 2
    @Lockroach 我对公司高层说话, 我被开除了摇篮系统就会一键删除公司所有数据库,现在我将带着纸箱离开公司
    quicksandznzn
        32
    quicksandznzn  
       9h 17m ago
    Apple Watch + BLEUnlock 人走自动锁屏 根据蓝牙实现的
    wu67
        33
    wu67  
       9h 7m ago   ❤️ 2
    不是哥们, 你们用 AI 开工之前不搜一下有没有成品吗? 何必浪费 token?

    https://github.com/ts1/BLEUnlock#readme
    bearbest
        34
    bearbest  
    PRO
       9h 4m ago
    设置个触发角,一划就完事了
    SakuraYuki
        35
    SakuraYuki  
       9h 1m ago
    直接用 wifi 或者蓝牙不就能做到了
    bush911
        36
    bush911  
       9h 0m ago via Android
    程序员 V5 ,我都是用手机蓝牙配对,当手机离开电脑几分钟就自动锁屏
    junas7
        37
    junas7  
       8h 54m ago
    @shalou8023 万一楼主公司抓到一次就要乐捐一次呢🐶
    SeanChense
        38
    SeanChense  
       8h 44m ago
    广播纪元?
    liegu0317
        39
    liegu0317  
       7h 24m ago
    “不跳就锁”,佩服楼主才华
    tf2
        40
    tf2  
       7h 21m ago
    快做个约会系统。遇到心动的人就直接匹配加好友。
    mqnu00
        41
    mqnu00  
       6h 55m ago
    考虑 windows 吗
    irobbin
        42
    irobbin  
       6h 45m ago
    我做了一个识别屏幕前人脸的自动锁屏
    话说,op 不会跟我是一个公司的吧
    ddddd0
        43
    ddddd0  
       6h 30m ago
    很好的想法
    youknowsomething
        44
    youknowsomething  
       6h 26m ago
    人在传感器行不行?
    lswlray
        45
    lswlray  
       6h 8m ago
    这属于制度管理问题,想要通过技术来解决,往往的结局是出力不讨好。
    runking
        46
    runking  
       5h 48m ago
    willygeek007
        47
    willygeek007  
       5h 16m ago
    点子挺好的,可惜已经有人实现好轮子了。
    isnullstring
        48
    isnullstring  
       3h 45m ago
    心率广播耗电,开一整天的话,手环要两天一冲或者一天一冲
    archxm
        49
    archxm  
       3h 44m ago
    用电脑时,不喜欢戴手表,戴左手影响敲键盘,戴右手影响鼠标。
    obeyatonce
        50
    obeyatonce  
       3h 40m ago via Android
    @Lockroach 心脏停跳就删库是吧
    Dwayne
        51
    Dwayne  
       3h 34m ago
    🐶我比较需要人走关闭显示器的
    Chicagoake
        52
    Chicagoake  
       3h 33m ago
    @JerryCat 这个好用,离远了自动锁,回来自动解锁,有几次同事都被我吓到了,离工位几步远,电脑自动解锁。就是纯靠蓝牙信号偶尔会有 bug ,就在电脑前突然就锁了。
    Chicagoake
        53
    Chicagoake  
       3h 31m ago
    @finull 有时候能读到设备的名称,有时候又读不到,挺烦的,工位弄这个一大堆设备。
    8355
        54
    8355  
       2h 38m ago
    工资没有暗示到帐就主动删库
    user9121
        55
    user9121  
       44 mins ago
    买一个带压力传感器的坐垫.毫秒级响应.哈哈
    billccn
        56
    billccn  
       34 mins ago
    BLE 手表/手环除了耗电以外如果忘了带也白搭。

    检测人存在用毫米波雷达最准了,呼吸频率都能检测,成品几百-几千块。还能实现后边来人自动最小化摸鱼窗口等功能。
    ql5435
        57
    ql5435  
       17 mins ago
    我个人还是习惯用快捷键锁屏,这个最简单靠谱。不过整体思路确实很赞,实现也扎实,就是实用性和手表续航的问题还是得再权衡一下
    docx
        58
    docx  
       1 min ago via iPhone
    我记得不是有个蓝牙锁屏的方案吗,带个手环或者手机的,人走动后带走手机,检测不到蓝牙就自动上锁。心率监测太费电了。
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   3186 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 184ms · UTC 12:36 · PVG 20:36 · LAX 05:36 · JFK 08:36
    ♥ Do have faith in what you're doing.