🐱 算神的小窝 🤓

使用C编写一个用于接收AmazfitBalance智能手表的心率广播数据的程序.md


CreationTime:7/27/2025 6:45:13 PM LastAccessTime:8/21/2025 3:44:03 PM


使用C#编写一个用于接收Amazfit Balance智能手表的心率广播数据的程序

前言

智能手表通过蓝牙低能耗(BLE)广播心率数据,因此我们可以使用C#的蓝牙库来扫描和接收Amazfit Balance智能手表广播数据。

为什么是Amazfit Balance智能手表

因为本人目前只有Amazfit Balance智能手表,哈哈。理论上只要手表或手环支持BLE心率广播功能,且完全符合公开的标准,均可以使用此手法来接收解码广播数据。其他品牌各位看官可以自己动手尝试下。

项目新建

  1. 新建一个.net 9控制台项目
  2. 修改.csprojTargetFramework内容修改为
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>

因为需要使用Windows SDK的专有命名空间Windows.Devices.Bluetooth来访问蓝牙,所以指定为windows独占,暂时无法跨平台。

程序本体

using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.Advertisement;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Storage.Streams;

namespace AmazfitHeartRateReceiver;

internal class Program
{
    // 用于存储检测到的手表设备
    private static readonly Dictionary<ulong, BluetoothLEDevice> devices = [];
    static void Main(string[] args)
    {
        Console.WriteLine("正在启动Amazfit Balance心率接收程序...");
        StartHeartRateScanner();
        Console.ReadLine(); // 保持程序运行
    }
    static void StartHeartRateScanner()
    {
        var watcher = new BluetoothLEAdvertisementWatcher
        {
            ScanningMode = BluetoothLEScanningMode.Active
        };

        // 添加心率服务过滤
        watcher.AdvertisementFilter.Advertisement.ServiceUuids.Add(BluetoothUuidHelper.FromShortId(0x180D));

        watcher.Received += async (sender, args) =>
        {
            try
            {
                // 获取蓝牙设备
                var device = await BluetoothLEDevice.FromBluetoothAddressAsync(args.BluetoothAddress);
                if (device == null) return;

                // 检查是否已处理过该设备
                if (devices.ContainsKey(device.BluetoothAddress)) return;
                devices.Add(device.BluetoothAddress, device);

                Console.WriteLine($"检测到设备: {device.Name} ({device.BluetoothAddress:X})");

                // 获取心率服务
                var hrService = (await device.GetGattServicesForUuidAsync(BluetoothUuidHelper.FromShortId(0x180D))).Services[0];
                if (hrService == null) return;

                // 获取心率特征值
                var hrCharacteristic = (await hrService.GetCharacteristicsForUuidAsync(BluetoothUuidHelper.FromShortId(0x2A37))).Characteristics[0];
                if (hrCharacteristic == null) return;

                // 启用特征值通知
                hrCharacteristic.ValueChanged += HeartRateValueChanged;
                await hrCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
                    GattClientCharacteristicConfigurationDescriptorValue.Notify);

                Console.WriteLine("已订阅心率数据通知");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"错误: {ex.Message}");
            }
        };

        watcher.Start();
        Console.WriteLine("扫描已启动,等待Amazfit Balance手表广播...");
    }

    private static void HeartRateValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
    {
        try
        {
            using var reader = DataReader.FromBuffer(args.CharacteristicValue);
            var heartRate = ParseHeartRateValue(reader);
            Console.WriteLine($"[{DateTime.Now:T}] 心率: {heartRate} BPM");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"解析错误: {ex.Message}");
        }
    }

    private static int ParseHeartRateValue(DataReader reader)
    {
        reader.ByteOrder = ByteOrder.LittleEndian;

        // 读取标志位
        byte flags = reader.ReadByte();
        bool is16bit = (flags & 0x01) != 0;

        return is16bit ? reader.ReadUInt16() : reader.ReadByte();
    }
}

程序说明

  1. 工作原理
    • 使用蓝牙LE广播扫描发现附近设备
    • 通过心率服务UUID(0x180D)过滤设备
    • 订阅心率特征值(0x2A37)的通知
    • 解析心率数据并实时显示
    • 关于其他的UUID和特征值,可以到蓝牙技术联盟官网上面找
  2. 关键组件
    • BluetoothLEAdvertisementWatcher:扫描BLE设备广播
    • GattCharacteristic.ValueChanged:接收心率数据变化通知
    • 心率数据解析:根据BLE规范解析标志位和数据格式
  3. 手表设置
    1. 手机进入「Zepp」应用
    2. 打开「设备」>「通用」>「蓝牙广播」
    3. 启用「开启蓝牙广播」
    4. 这样手表上的APP会多一个「心率推送」的,点开它 IMG_20250727_180248
    5. 点开后,同步打开PC端的扫描,看到蓝牙已经连接上就不用管了,如果长时间丢失连接,手表出于节能的目的会自动关闭广播功能
  4. 注意事项
    • 确保系统蓝牙已开启
    • 手表无需与电脑配对
    • 手表与电脑距离应在10米内
    • 心率值格式根据BLE规范解析(8位或16位)

运行效果

image-20250727180649637

开源项目

基于上述的原理,我进一步做成了一个带GUI的WPF开源项目,欢迎大家去Star

https://github.com/lishewen/AmazfitHeartRateReceiver

效果图

image-20250727181740866

Web服务

在这个开源项目中,还做了一个小功能,见启动Web服务按钮

点击后会拉起一个内置的ASP.Net Core的小型网站,默认地址为http://localhost:5001/

里面只有一个页面,是一个300px*300px的实时心率小卡片

OBS的小卡片

这个小卡片可用于在OBS直播中显示实时心率,添加一个浏览器源,按下面这样配置一下

image-20250727182608588

这样就可以在直播魔兽打本的时候显示自己的心率了

在WPF中拉起Web服务的实现细节

  1. 安装Nuget包Microsoft.Extensions.Hosting
dotnet add package Microsoft.Extensions.Hosting
  1. 修改.csproj,加入
	<ItemGroup>
		<FrameworkReference Include="Microsoft.AspNetCore.App"/>
	</ItemGroup>
  1. 实现代码
private void StartWebServer()
{
    // 创建并启动Web主机
    _webHost = Host.CreateDefaultBuilder()
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseUrls(_webServerUrl);
            webBuilder.Configure(app => { /* 配置中间件 */ });
        })
        .Build();

    _webServerTask = _webHost.StartAsync(_cancellationTokenSource.Token);
    
    // 自动打开默认浏览器
    System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
    {
        FileName = _webServerUrl,
        UseShellExecute = true
    });
}

private void StopWebServer()
{
    // 停止并释放Web主机资源
    _cancellationTokenSource?.Cancel();
    _webHost?.StopAsync().Wait();
    _webHost?.Dispose();
    _webHost = null;
}
An unhandled error has occurred. Reload 🗙