技术 / macOS

macOS 通过本地 USB 定位开启 Apple Watch S9 高血压通知

记录在 macOS 上通过本地 MFi-v3 认证和 USB iAP2 定位,为 Apple Watch S9 完成高血压通知设置的过程。附桥接程序源码、操作步骤、排错方法,以及停止后恢复真实定位的验证。

适用环境:macOS 27.0.1 · iPhone 16 Pro Max / iOS 27.2 · Apple Watch Series 9 · 最后验证:2026/10/15

文章目录

这次用 Mac 给自己的 Apple Watch Series 9 开启了高血压通知。过程是先通过 USB 给配对的 iPhone 提供香港定位,再进入“健康”完成设置,最后停止定位并确认恢复。

最终检查了三件事:地图蓝点确实到了香港;健康 App 显示高血压通知已设置;停止程序后,地图回到真实位置,通知仍然开启。认证和定位发送都在本机完成,没有使用 AnyGo 云端。

下面按本次实际操作整理,适合愿意使用终端、能自行判断错误输出的读者。

这是一次个人设备实测,不保证其他系统版本、销售地区或后续更新仍然有效。成功完成设置,也不能证明后续一定会收到通知。苹果说明该功能按 30 天周期分析心率传感器数据,并非直接测量血压,也不能用于诊断或管理高血压。资格条件和功能说明以苹果官方文档为准。

实测环境与前提

项目 本次环境
Mac Apple 芯片,macOS 27.0.1
iPhone iPhone 16 Pro Max,iOS 27.2
Apple Watch Series 9,已与这台 iPhone 配对
watchOS 本次没有单独记录准确版本
连接方式 USB 数据线,iPhone 已解锁并信任 Mac
构建环境 JDK 21、Python 3.12、uv、Git
目标位置 香港中环附近,北纬 22.2793、东经 114.1628

先检查正常开通条件:苹果列出的机型包括 Series 9;还需符合年龄等资格条件、开启手腕检测,并满足当地功能可用性要求。设置时按真实情况填写健康问题,不要为了通过向导而修改既往诊断等信息。

如果在“健康”里已经可以正常开通,直接走官方设置即可。本文记录的是通过临时香港定位完成设置的实验路径。

这套方法做了什么

Mac 通过系统的 USB 配对服务打开 iPhone 的 com.apple.carkit.service,在这个连接上协商 iAP2,完成配件声明和 MFi-v3 认证。手机请求外部定位后,程序每秒发送一对香港位置的 GPGGA、GPRMC 句子。

这里参考了 PlayPort 的协议代码。PlayPort 本身是无线 CarPlay 接收器,README 明确说明不支持有线 USB CarPlay;本文另写了 USB CarKit 桥接程序,复用它的协议模块。单独安装并启动 PlayPort,不能直接得到本文的 USB 定位功能。

本地认证需要配件证书与匹配的私钥。本次使用的是 DiPlay v0.2.7 APK 中的实验性认证数据。上游说明这组数据来自公开 Carlinkit 固件,并非为本文程序新签发的凭据,未来可能被拒绝或撤销。源码附件不包含这些文件,也不要把它们重新打包发布。

准备源码与环境

1. 下载桥接程序

下载本文附件:本地 USB 定位桥接程序源码。

压缩包包含本次使用的 Kotlin 程序、Python 桥接、构建配置、依赖版本清单和两项本地测试,不含 APK、证书、私钥或设备配对记录。

下载后,在终端进入下载目录并校验:

cd ~/Downloads
shasum -a 256 s9-local-location-source-20261015.zip

应得到:

12726513840d0c8e0b94069f71695ccca00d85cdedc683986e6d68f247bde02e

解压并进入项目目录:

unzip s9-local-location-source-20261015.zip
cd s9-local-location

后面的命令都在这个目录执行。本文命令面向 Apple 芯片 Mac,随附启动脚本使用 /opt/homebrew 下的 JDK 路径。

2. 安装运行依赖

Mac 需要先有 Homebrew 和 Git;git --version 若提示安装命令行工具,先完成系统提示的安装。

brew install openjdk@21 uv
uv venv .venv --python 3.12
uv pip install --python .venv/bin/python -r requirements.lock.txt

只为当前终端设置 Java 环境,不需要改系统 Java 配置:

export JAVA_HOME="$(brew --prefix openjdk@21)/libexec/openjdk.jdk/Contents/Home"
"$JAVA_HOME/bin/java" -version

3. 准备固定版本的协议源码

mkdir -p vendor
git clone https://github.com/youcci/playport.git vendor/playport
git -C vendor/playport checkout --detach 9a0882dd0ffe48e467b59d58b12d81391df55ade

固定提交是为了对应本次实测代码,避免上游后续修改带来接口差异。这一步只取协议依赖,不需要启动 PlayPort 的无线 CarPlay 服务或构建它的网页端。

准备本地认证文件

下载本次使用的 APK:

curl -fL --retry 1 -o DiPlay-0.2.7.apk \
  https://github.com/shihabal3amri/DiPlay/releases/download/v0.2.7/DiPlay-0.2.7.apk
shasum -a 256 DiPlay-0.2.7.apk

本次文件的 SHA-256:

27b434462da7e78b1ff0987d129ed0a4b0a0e56beceaf8ec221d47150d4675bd

校验一致后,只提取指定目录:

mkdir -p identity/offline-mfi
chmod 700 identity identity/offline-mfi
unzip -j DiPlay-0.2.7.apk 'assets/offline-mfi/*' -d identity/offline-mfi
chmod 600 identity/offline-mfi/certificate.p7b identity/offline-mfi/identity.pk8
chmod 600 DiPlay-0.2.7.apk

目录中应有:

identity/offline-mfi/
├── certificate.p7b
└── identity.pk8

文件名和目录结构要保持一致。程序只在本机读取它们,用私钥签署手机发来的认证挑战;博客不提供私钥内容。

编译并检查手机连接

先构建并运行测试:

vendor/playport/gradlew -p . installDist test --console=plain
./run-local.sh --self-test

本次上游协议测试 89 项、本地 NMEA 封装测试 2 项全部通过。自检也确认了证书和私钥匹配,能够产生 MFi-v3 签名。

这里的 PASS 只证明本地文件和编码检查通过。是否被 iPhone 接受,要看连接后的认证结果。

把与手表配对的 iPhone 用数据线接到 Mac,解锁,并在出现提示时选择“信任”。检查设备数量:

.venv/bin/python carkit_bridge.py list

本次输出:

{ "usb_iphone_count": 1 }

程序要求恰好连接一台 USB iPhone。数量为 0 时先检查线缆、解锁状态与信任提示;连接多台时,先断开其他设备。

开始发送香港定位

运行 10 分钟的定位会话:

./run-local.sh 600

保持这个终端窗口打开。600 是会话秒数,可以随时用 Ctrl+C 提前停止;到时也会自动关闭连接。运行期间,其他依赖系统定位的应用也可能采用临时位置,完成后应先确认真实定位恢复。

正常连接后会依次看到类似输出,以下省略时间戳:

Local com.apple.carkit.service opened
iAP2 link ready
Identification ACCEPTED
MFi-v3 authentication ACCEPTED by iPhone
iPhone requested accessory location; parameter IDs=[0, 1, 32769, 2, 32770]
HK NMEA sent: count=1; Maps/Health acceptance still requires observation

其中认证成功和 HK NMEA sent 都不能代替地图验证。手机也可能暂时停止定位订阅,程序会等待它再次请求,而不是无条件发送。

接着在 iPhone 上操作:

  1. 打开苹果“地图”。
  2. 进入“设置 → 隐私与安全性 → 定位服务”。
  3. 关闭定位服务,等待 10 秒,再重新开启。
  4. 返回地图,点“当前位置”,确认蓝点是否到了香港。

本次生效后的地图如下,蓝点位于香港中环附近:

苹果地图中的当前位置蓝点位于香港中环附近

如果蓝点仍在真实位置,就还没有完成这一步。此时先排查定位,不要仅凭终端的发送计数判断已成功。

在健康中完成设置

确认香港定位后,保持程序运行,在配对的 iPhone 上进入:

健康 → 右上角头像 → 健康清单 → 高血压通知

按真实情况回答年龄、是否曾确诊高血压等问题,完成后续说明页面。

本次最终显示“高血压通知功能软件已设置”:

健康 App 显示高血压通知功能软件已设置,Apple Watch 将开始寻找可能是高血压的模式

如果仍提示所在地区不支持,记录具体提示和系统版本。本次结果不能推导成所有型号、版本或地区都能开通,也不要修改健康资料来绕过资格问题。

停止程序并验证恢复

完成设置后,回到 Mac 的终端按 Ctrl+C,等待退出。程序会关闭自己持有的 CarKit 连接。

本次停止时累计发送了 92 对 NMEA,日志包含:

STOPPED: CarKit closed, sent=92. Real-location restoration and Health state need on-device verification.
CarKit closed; accessory location feed stopped

可以检查相关进程是否还在:

pgrep -fl 'LocalLocationKt|carkit_bridge.py bridge'

相关进程退出后,再在手机上完成两项检查:

  • 打开地图并点“当前位置”,确认蓝点恢复真实位置。
  • 回到健康清单,确认高血压通知仍显示开启。

本次两项都已确认。若位置还停留在香港,先断开 USB、重新打开地图;仍未恢复时,可以重新开关定位服务或重启 iPhone,恢复后再检查健康设置。后面这些是恢复排查步骤,本次没有用到重启手机。

程序停止、真实定位恢复、通知保持开启,是三个需要分别验证的状态。

本次遇到的两个问题

认证通过,但地图没有变化

初版直接复用了上游生成的 CRLF 拼接字符串,把 GPGGA 和 GPRMC 一起放进一个以 NUL 结尾的参数。日志显示认证成功、开始发送,但地图仍然显示真实位置。

修正版做了两处调整:每条 NMEA 句子单独封装,分别以 NUL 结尾;为 RMC 补上自主定位模式字段,并重新计算校验和。之后重新连接,再关闭定位服务 10 秒并开启,地图才显示香港。

附件包含的是修正版。因为代码修正和定位服务重启共同发生,不能单独认定某一项就是决定因素。

定位服务收到数据,仍有筛选失败日志

手机日志确认 locationd 收到了 NMEA,同时也出现过:

GPRMC NMEA Screening failed

检查时 Mac 与手机时钟仅差约 0.25 秒,句子时间戳也接近当前时间,未发现明显时钟偏差。这个日志的具体原因没有完全确定;即使地图后来已显示香港,也不能据此断言所有发送的数据都被定位服务采用。

因此排查时应逐层确认:手机是否接受认证、是否请求定位、地图是否改变、健康是否完成设置。最后两项需要看手机界面。

清理实验文件

我在确认真实定位恢复、通知仍开启后,删除了 APK、提取的证书与私钥、虚拟环境、上游源码副本及构建缓存,只保留操作记录和自编代码。

读者完成后,也可以在确认程序退出后清理这个独立实验目录。APK 同样包含认证数据,不要只删除解压出的私钥而留下 APK,更不要把整个目录提交到公开仓库。

JDK、uv 等工具如果原本就已安装,继续保留即可;只有确认是此次新装、且没有其他软件使用时才考虑卸载。不要为清理一次实验而删除整个 ~/.gradle 或共享 Python 缓存。

后续如果系统更新、重新配对或功能状态变化,需要重新检查“健康”中的状态。本次恢复后的开关保持开启,不等于长期永久有效。

参考资料

本文附件代码按 GPL-3.0 提供;所用协议模块来自 PlayPort、DiPlay 与 xcertplay,认证数据不属于该源码许可证。