C# 蓝牙开发精髓:基础语法、协议解析与项目实战

前言

蓝牙技术作为短距离无线通信的核心方案,已广泛应用于智能硬件、工业控制、穿戴设备等场景。C# 凭借跨平台特性(.NET 6+)和丰富的蓝牙开发库,成为蓝牙应用开发的优选语言。但开发者常面临「基础语法混乱、协议理解不深、实战落地困难」等问题。

本文聚焦 C# 蓝牙开发的「精髓」,从「基础语法入门→核心协议解析→全场景项目实战」三层逻辑展开,既覆盖经典蓝牙(RFCOMM)和低功耗蓝牙(BLE)的基础用法,又深入协议底层细节,最终通过工业传感器采集、智能穿戴数据同步、硬件控制三个实战项目,帮助开发者快速掌握 C# 蓝牙开发的核心能力,实现从入门到实战的跨越。

一、C# 蓝牙开发基础:语法与核心库

1. 开发环境搭建

(1)基础配置

开发工具:Visual Studio 2022(勾选「.NET 桌面开发」「跨平台应用开发」 workload);框架版本:.NET 6 或 .NET 7(跨平台支持最优);硬件要求:支持蓝牙 4.0+ 的 PC(或 USB 蓝牙适配器)、蓝牙测试设备(HC-05 模块、BLE 传感器)。

(2)核心库选型与安装

C# 蓝牙开发库众多,按场景精准选型:

库名称 支持平台 协议支持 核心优势 安装命令
32feet.NET Windows(优先)、Linux 经典蓝牙、BLE 经典蓝牙 RFCOMM 稳定,文档丰富
Install-Package 32feet.NET
Plugin.BLE Windows、Android、iOS、Linux BLE 为主 跨平台支持完善,GATT 操作简洁
Install-Package Plugin.BLE
InTheHand.Net.Bluetooth Windows 10+/IoT Core 经典蓝牙、BLE 32feet.NET 升级版,支持 BLE 高级特性
Install-Package InTheHand.Net.Bluetooth

选型建议:快速开发 Windows 经典蓝牙应用用 32feet.NET;跨平台 BLE 应用用 Plugin.BLE;Windows IoT 设备用 InTheHand.Net.Bluetooth。

2. 基础语法:蓝牙设备扫描与连接

(1)经典蓝牙(RFCOMM)基础:HC-05 模块扫描与连接

经典蓝牙本质是「无线串口」,适用于工业设备通信,以 HC-05 模块为例:


using System;
using System.Text;
using System.Threading;
using InTheHand.Net.Bluetooth;
using InTheHand.Net.Sockets;

namespace BluetoothClassicDemo
{
    class ClassicBluetoothDemo
    {
        // 经典蓝牙客户端
        private readonly BluetoothClient _bluetoothClient = new BluetoothClient();
        // 连接状态
        private bool _isConnected = false;

        /// <summary>
        /// 扫描经典蓝牙设备(HC-05)
        /// </summary>
        public BluetoothDeviceInfo ScanDevice()
        {
            try
            {
                // 检查蓝牙是否开启
                var radio = BluetoothRadio.Default;
                if (radio == null || radio.State != RadioState.On)
                {
                    Console.WriteLine("蓝牙未开启!");
                    return null;
                }

                Console.WriteLine("扫描 HC-05 设备...");
                // 扫描周边蓝牙设备(最多返回 10 个)
                var devices = _bluetoothClient.DiscoverDevices(10, true, true, true, false);

                foreach (var device in devices)
                {
                    // 过滤 HC-05 设备(名称含 "HC-05")
                    if (device.DeviceName.Contains("HC-05"))
                    {
                        Console.WriteLine($"发现设备:{device.DeviceName}(MAC:{device.DeviceAddress})");
                        return device;
                    }
                }

                Console.WriteLine("未找到 HC-05 设备!");
                return null;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"扫描失败:{ex.Message}");
                return null;
            }
        }

        /// <summary>
        /// 连接 HC-05 模块(默认配对码 1234)
        /// </summary>
        public bool Connect(BluetoothDeviceInfo device)
        {
            try
            {
                // 经典蓝牙串口服务 UUID(固定值)
                var serviceUuid = BluetoothService.SerialPort;
                // 连接设备
                _bluetoothClient.Connect(device.DeviceAddress, serviceUuid);
                _isConnected = true;
                Console.WriteLine("设备连接成功!");

                // 启动数据接收线程
                new Thread(ReceiveDataLoop) { IsBackground = true }.Start();
                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"连接失败:{ex.Message}(请确认设备已配对,配对码 1234)");
                return false;
            }
        }

        /// <summary>
        /// 数据接收循环
        /// </summary>
        private void ReceiveDataLoop()
        {
            var stream = _bluetoothClient.GetStream();
            byte[] buffer = new byte[1024];
            StringBuilder sb = new StringBuilder();

            while (_isConnected)
            {
                try
                {
                    if (stream.DataAvailable)
                    {
                        int bytesRead = stream.Read(buffer, 0, buffer.Length);
                        string data = Encoding.UTF8.GetString(buffer, 0, bytesRead);
                        sb.Append(data);

                        // 按换行符分割完整数据(IoT 设备常用结束标志)
                        if (data.Contains("
"))
                        {
                            string fullData = sb.ToString().Trim();
                            Console.WriteLine($"收到数据:{fullData}");
                            sb.Clear();
                        }
                    }
                    Thread.Sleep(100);
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"接收数据异常:{ex.Message}");
                    break;
                }
            }
            _isConnected = false;
        }

        /// <summary>
        /// 发送数据到设备
        /// </summary>
        public void SendData(string data)
        {
            if (!_isConnected)
            {
                Console.WriteLine("未连接设备!");
                return;
            }

            try
            {
                var stream = _bluetoothClient.GetStream();
                byte[] dataBytes = Encoding.UTF8.GetBytes(data + "
"); // 加换行符作为结束标志
                stream.Write(dataBytes, 0, dataBytes.Length);
                stream.Flush();
                Console.WriteLine($"发送数据:{data}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"发送数据失败:{ex.Message}");
            }
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        public void Disconnect()
        {
            _isConnected = false;
            _bluetoothClient.Close();
            Console.WriteLine("设备已断开连接!");
        }

        // 调用示例
        static void Main(string[] args)
        {
            var demo = new ClassicBluetoothDemo();
            // 扫描设备
            var device = demo.ScanDevice();
            if (device != null)
            {
                // 连接设备
                if (demo.Connect(device))
                {
                    // 发送控制指令
                    demo.SendData("LED_ON");
                    // 持续运行
                    Console.WriteLine("按任意键退出...");
                    Console.ReadKey();
                    demo.Disconnect();
                }
            }
        }
    }
}
(2)BLE 基础:BLE 传感器扫描与连接

BLE 适用于低功耗设备,核心基于 GATT 协议,以 BLE 温湿度传感器为例:


using System;
using System.Threading.Tasks;
using Plugin.BLE;
using Plugin.BLE.Abstractions.Contracts;
using Plugin.BLE.Abstractions.EventArgs;

namespace BLEBasicDemo
{
    class BLEBasicDemo
    {
        // BLE 适配器(管理设备扫描、连接)
        private readonly IAdapter _adapter = CrossBluetoothLE.Current.Adapter;
        // 已连接设备
        private IDevice _connectedDevice;
        // 温湿度特征值(数据传输核心)
        private ICharacteristic _tempHumChar;

        // 传感器 GATT 配置(示例 UUID,需按实际设备修改)
        private readonly Guid _serviceUuid = Guid.Parse("0000ffe0-0000-1000-8000-00805f9b34fb");
        private readonly Guid _charUuid = Guid.Parse("0000ffe1-0000-1000-8000-00805f9b34fb");

        /// <summary>
        /// 扫描 BLE 设备
        /// </summary>
        public async Task ScanDevicesAsync()
        {
            try
            {
                // 检查蓝牙是否开启
                if (!CrossBluetoothLE.Current.IsOn)
                {
                    Console.WriteLine("蓝牙未开启!");
                    return;
                }

                Console.WriteLine("扫描 BLE 设备...");
                // 订阅设备发现事件
                _adapter.DeviceDiscovered += (s, e) =>
                {
                    // 过滤目标传感器(名称含 "TempSensor")
                    if (!string.IsNullOrEmpty(e.Device.Name) && e.Device.Name.Contains("TempSensor"))
                    {
                        Console.WriteLine($"发现设备:{e.Device.Name}(ID:{e.Device.Id})");
                        _connectedDevice = e.Device;
                    }
                };

                // 扫描 5 秒后停止
                await _adapter.StartScanningForDevicesAsync();
                await Task.Delay(5000);
                await _adapter.StopScanningForDevicesAsync();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"扫描失败:{ex.Message}");
            }
        }

        /// <summary>
        /// 连接设备并订阅数据
        /// </summary>
        public async Task ConnectAndSubscribeAsync()
        {
            if (_connectedDevice == null)
            {
                Console.WriteLine("未找到目标设备!");
                return;
            }

            try
            {
                Console.WriteLine("连接设备...");
                // 连接设备
                await _adapter.ConnectToDeviceAsync(_connectedDevice);

                // 发现 GATT 服务
                var service = await _connectedDevice.GetServiceAsync(_serviceUuid);
                if (service == null)
                {
                    Console.WriteLine("未找到目标服务!");
                    return;
                }

                // 发现特征值
                _tempHumChar = await service.GetCharacteristicAsync(_charUuid);
                if (_tempHumChar == null)
                {
                    Console.WriteLine("未找到目标特征值!");
                    return;
                }

                // 订阅特征值通知(设备主动推送数据)
                await _tempHumChar.StartUpdatesAsync();
                _tempHumChar.ValueUpdated += TempHumChar_ValueUpdated;
                Console.WriteLine("数据订阅成功,等待设备推送...");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"连接失败:{ex.Message}");
            }
        }

        /// <summary>
        /// 数据更新回调(设备推送数据时触发)
        /// </summary>
        private void TempHumChar_ValueUpdated(object sender, CharacteristicUpdatedEventArgs e)
        {
            try
            {
                // 解析字节数据(示例:字节0-1=温度,字节2-3=湿度,小端序)
                byte[] data = e.Characteristic.Value;
                float temperature = BitConverter.ToSingle(data, 0);
                float humidity = BitConverter.ToSingle(data, 4);
                Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 温度:{temperature:F1}℃,湿度:{humidity:F1}%");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"数据解析失败:{ex.Message}");
            }
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        public async Task DisconnectAsync()
        {
            if (_tempHumChar != null)
            {
                await _tempHumChar.StopUpdatesAsync();
                _tempHumChar.ValueUpdated -= TempHumChar_ValueUpdated;
            }
            if (_connectedDevice != null)
            {
                await _adapter.DisconnectDeviceAsync(_connectedDevice);
                Console.WriteLine("设备已断开连接!");
            }
        }

        // 调用示例
        static async Task Main(string[] args)
        {
            var demo = new BLEBasicDemo();
            // 扫描设备
            await demo.ScanDevicesAsync();
            // 连接并订阅数据
            await demo.ConnectAndSubscribeAsync();

            Console.WriteLine("按任意键退出...");
            Console.ReadKey();
            // 断开连接
            await demo.DisconnectAsync();
        }
    }
}

3. 基础语法核心总结

经典蓝牙:核心是「BluetoothClient + 数据流(NetworkStream)」,类比串口通信;BLE:核心是「Adapter + Device + Service + Characteristic」,基于 GATT 层级结构;数据处理:经典蓝牙通过流读写字符串/字节;BLE 通过特征值的「通知订阅」或「读写」获取数据。

二、核心协议解析:经典蓝牙 vs BLE

1. 协议底层差异

协议类型 经典蓝牙(RFCOMM) BLE(低功耗蓝牙)
核心定位 无线串口通信 低功耗数据传输
传输速率 1-3 Mbps 0.1-1 Mbps
功耗水平 中高(持续连接耗电) 极低(适合电池供电设备)
通信方式 点对点长连接 广播、点对点长连接
核心协议 RFCOMM(串口仿真) GATT(通用属性配置文件)
适用场景 工业设备、PLC、HC-05 模块 穿戴设备、传感器、智能硬件

2. 经典蓝牙 RFCOMM 协议解析

RFCOMM 协议的核心是「将蓝牙连接仿真为串口」,让设备按串口协议(如 9600 波特率、N-8-1 校验)通信:

波特率:经典蓝牙设备需统一波特率(如 HC-05 默认 9600),否则数据传输乱码;数据帧格式:通常以「起始符+数据+校验位+结束符」组成(如
0xAA 0x01 0x23 0xBB
);常见问题:数据粘包(多个数据包合并)、丢包(传输速率不匹配),需通过「超时判断」「结束符分割」解决。

数据粘包解决方案示例:

// 改进的数据接收逻辑(解决粘包)
private void ReceiveDataLoop()
{
    var stream = _bluetoothClient.GetStream();
    byte[] buffer = new byte[1024];
    List<byte> dataBuffer = new List<byte>();
    const byte StartByte = 0xAA; // 起始符
    const byte EndByte = 0xBB;   // 结束符

    while (_isConnected)
    {
        try
        {
            if (stream.DataAvailable)
            {
                int bytesRead = stream.Read(buffer, 0, buffer.Length);
                dataBuffer.AddRange(buffer.Take(bytesRead));

                // 循环解析完整数据包
                while (dataBuffer.Count >= 3) // 最小数据包长度(起始符+1字节数据+结束符)
                {
                    // 找到起始符索引
                    int startIndex = dataBuffer.IndexOf(StartByte);
                    if (startIndex == -1)
                    {
                        dataBuffer.Clear(); // 无起始符,清空缓存
                        break;
                    }

                    // 找到结束符索引
                    int endIndex = dataBuffer.IndexOf(EndByte, startIndex);
                    if (endIndex == -1)
                    {
                        break; // 未找到结束符,等待后续数据
                    }

                    // 提取完整数据包(包含起始符和结束符)
                    byte[] fullData = dataBuffer.GetRange(startIndex, endIndex - startIndex + 1).ToArray();
                    // 移除已解析的数据
                    dataBuffer.RemoveRange(0, endIndex + 1);

                    // 解析数据(示例:中间字节为有效数据)
                    string data = BitConverter.ToString(fullData.Skip(1).Take(fullData.Length - 2).ToArray());
                    Console.WriteLine($"收到完整数据包:{data}");
                }
            }
            Thread.Sleep(100);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"接收数据异常:{ex.Message}");
            break;
        }
    }
}

3. BLE GATT 协议深度解析

GATT 是 BLE 数据交互的标准协议,核心是「服务-特征值-描述符」三层结构:

(1)核心层级详解

服务(Service):设备的功能模块(如心率服务、电池服务),用 UUID 唯一标识;
标准服务:蓝牙 SIG 定义(UUID 前缀
000018xx
,如心率服务
0000180d-...
);自定义服务:厂商定义(128 位 UUID,如
0000ffe0-...
);
特征值(Characteristic):服务下的具体数据项(如心率值、控制指令),支持 5 种操作:

操作类型 作用 示例场景
Read(读取) 客户端主动获取数据 查询设备电量
Write(写入带响应) 客户端发送数据,设备返回确认 发送控制指令(确保送达)
Write Without Response 客户端发送数据,设备不返回确认 批量推送数据(速度快)
Notify(通知) 设备主动推送数据,无需客户端确认 心率实时更新
Indicate(指示) 设备主动推送数据,需客户端确认 重要告警信息

描述符(Descriptor):特征值的补充信息,常用「客户端特征配置描述符」(UUID:
00002902-...
),用于启用通知/指示。

(2)GATT 协议实战技巧

启用通知:需向
00002902-...
描述符写入
0x0001
(启用通知)或
0x0002
(启用指示);MTU 协商:BLE 最大传输单元(MTU)默认 23 字节,协商后可提升至 517 字节,减少分包传输;数据解析:BLE 设备数据多为小端序(Little-Endian),C# 中需用
BitConverter.ToUInt16(data, 0)
解析(小端序)。

启用 BLE 通知示例:

/// <summary>
/// 启用特征值通知(关键步骤)
/// </summary>
private async Task EnableNotificationAsync(ICharacteristic characteristic)
{
    // 获取客户端特征配置描述符
    var descriptor = await characteristic.GetDescriptorAsync(Guid.Parse("00002902-0000-1000-8000-00805f9b34fb"));
    if (descriptor == null)
    {
        Console.WriteLine("未找到通知配置描述符!");
        return;
    }

    // 写入 0x0001 启用通知(0x0002 启用指示)
    byte[] enableNotify = { 0x01, 0x00 }; // 小端序
    await descriptor.WriteAsync(enableNotify);
    Console.WriteLine("通知已启用!");
}

三、项目实战:三大核心场景落地

1. 实战一:工业传感器数据采集(经典蓝牙 RFCOMM)

场景需求:

通过 HC-05 模块连接工业温湿度传感器,实时采集温度、湿度数据,处理粘包和丢包,存储历史数据。

核心代码实现:

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using InTheHand.Net.Bluetooth;
using InTheHand.Net.Sockets;

namespace IndustrialSensorCollector
{
    // 传感器数据模型
    public class SensorData
    {
        public DateTime CollectTime { get; set; }
        public float Temperature { get; set; }
        public float Humidity { get; set; }
    }

    class SensorCollector
    {
        private readonly BluetoothClient _bluetoothClient = new BluetoothClient();
        private bool _isConnected = false;
        private readonly List<SensorData> _historyData = new List<SensorData>();
        // 传感器协议配置
        private const byte StartByte = 0xAA;
        private const byte EndByte = 0xBB;
        private const int BaudRate = 9600; // 需与传感器一致

        /// <summary>
        /// 启动采集服务
        /// </summary>
        public void StartCollection()
        {
            // 扫描设备
            var device = ScanDevice();
            if (device == null) return;

            // 连接设备
            if (Connect(device))
            {
                // 持续采集,按 Ctrl+C 停止
                Console.WriteLine("采集已启动,按 Ctrl+C 停止...");
                Console.CancelKeyPress += (s, e) =>
                {
                    StopCollection();
                    e.Cancel = true;
                };
                while (_isConnected) Thread.Sleep(1000);
            }
        }

        /// <summary>
        /// 停止采集并保存数据
        /// </summary>
        public void StopCollection()
        {
            _isConnected = false;
            _bluetoothClient.Close();
            // 保存历史数据到文件
            SaveHistoryData("sensor_data.csv");
            Console.WriteLine("采集已停止,数据已保存!");
        }

        /// <summary>
        /// 解析传感器数据(协议:0xAA + 温度(2字节) + 湿度(2字节) + 校验位(1字节) + 0xBB)
        /// </summary>
        private SensorData ParseSensorData(byte[] data)
        {
            try
            {
                // 校验数据长度
                if (data.Length != 6) return null;

                // 校验位验证(前4字节异或)
                byte checkSum = (byte)(data[1] ^ data[2] ^ data[3] ^ data[4]);
                if (checkSum != data[5])
                {
                    Console.WriteLine("数据校验失败!");
                    return null;
                }

                // 解析温度(小端序,单位:0.1℃)
                float temperature = BitConverter.ToUInt16(data, 1) / 10.0f;
                // 解析湿度(小端序,单位:0.1%)
                float humidity = BitConverter.ToUInt16(data, 3) / 10.0f;

                return new SensorData
                {
                    CollectTime = DateTime.Now,
                    Temperature = temperature,
                    Humidity = humidity
                };
            }
            catch (Exception ex)
            {
                Console.WriteLine($"数据解析失败:{ex.Message}");
                return null;
            }
        }

        /// <summary>
        /// 保存历史数据到 CSV 文件
        /// </summary>
        private void SaveHistoryData(string filePath)
        {
            using (StreamWriter sw = new StreamWriter(filePath, false, System.Text.Encoding.UTF8))
            {
                // 写入表头
                sw.WriteLine("采集时间,温度(℃),湿度(%)");
                // 写入数据
                foreach (var data in _historyData)
                {
                    sw.WriteLine($"{data.CollectTime:yyyy-MM-dd HH:mm:ss},{data.Temperature:F1},{data.Humidity:F1}");
                }
            }
        }

        // 其他方法(ScanDevice、Connect、ReceiveDataLoop)参考前文,修改数据解析逻辑为 ParseSensorData
    }

    class Program
    {
        static void Main(string[] args)
        {
            var collector = new SensorCollector();
            collector.StartCollection();
        }
    }
}

2. 实战二:智能穿戴设备数据同步(BLE GATT)

场景需求:

通过 BLE 连接智能手环,同步心率、步数、卡路里数据,实时展示并支持 Excel 导出。

核心代码实现(基于 Plugin.BLE + NPOI):

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Plugin.BLE;
using Plugin.BLE.Abstractions.Contracts;
using Plugin.BLE.Abstractions.EventArgs;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using System.IO;

namespace WearableDataSync
{
    // 穿戴设备数据模型
    public class WearableData
    {
        public DateTime SyncTime { get; set; }
        public int HeartRate { get; set; } // 心率(次/分钟)
        public int StepCount { get; set; } // 步数(步)
        public float Calorie { get; set; } // 卡路里(千卡)
    }

    class WearableSyncDemo
    {
        private readonly IAdapter _adapter = CrossBluetoothLE.Current.Adapter;
        private IDevice _connectedDevice;
        private ICharacteristic _heartRateChar;
        private ICharacteristic _stepCalorieChar;
        private readonly List<WearableData> _syncData = new List<WearableData>();

        // 手环 GATT 配置(标准心率服务 + 自定义步数服务)
        private readonly Guid _heartRateServiceUuid = Guid.Parse("0000180d-0000-1000-8000-00805f9b34fb");
        private readonly Guid _heartRateCharUuid = Guid.Parse("00002a37-0000-1000-8000-00805f9b34fb");
        private readonly Guid _stepServiceUuid = Guid.Parse("0000f000-0000-1000-8000-00805f9b34fb");
        private readonly Guid _stepCharUuid = Guid.Parse("0000f001-0000-1000-8000-00805f9b34fb");

        /// <summary>
        /// 扫描并连接手环
        /// </summary>
        public async Task ConnectWearableAsync()
        {
            if (!CrossBluetoothLE.Current.IsOn)
            {
                Console.WriteLine("蓝牙未开启!");
                return;
            }

            Console.WriteLine("扫描智能手环...");
            _adapter.DeviceDiscovered += (s, e) =>
            {
                if (!string.IsNullOrEmpty(e.Device.Name) && e.Device.Name.Contains("Mi Band") || e.Device.Name.Contains("Huawei Band"))
                {
                    Console.WriteLine($"发现手环:{e.Device.Name}(ID:{e.Device.Id})");
                    _connectedDevice = e.Device;
                }
            };

            await _adapter.StartScanningForDevicesAsync();
            await Task.Delay(5000);
            await _adapter.StopScanningForDevicesAsync();

            if (_connectedDevice == null)
            {
                Console.WriteLine("未找到智能手环!");
                return;
            }

            // 连接设备并初始化服务
            await _adapter.ConnectToDeviceAsync(_connectedDevice);
            await InitGattServicesAsync();
        }

        /// <summary>
        /// 初始化 GATT 服务与特征值
        /// </summary>
        private async Task InitGattServicesAsync()
        {
            // 初始化心率服务
            var heartRateService = await _connectedDevice.GetServiceAsync(_heartRateServiceUuid);
            if (heartRateService != null)
            {
                _heartRateChar = await heartRateService.GetCharacteristicAsync(_heartRateCharUuid);
                if (_heartRateChar != null)
                {
                    await EnableNotificationAsync(_heartRateChar);
                    _heartRateChar.ValueUpdated += HeartRateChar_ValueUpdated;
                    Console.WriteLine("心率数据同步已启动!");
                }
            }

            // 初始化步数/卡路里服务
            var stepService = await _connectedDevice.GetServiceAsync(_stepServiceUuid);
            if (stepService != null)
            {
                _stepCalorieChar = await stepService.GetCharacteristicAsync(_stepCharUuid);
                if (_stepCalorieChar != null)
                {
                    await EnableNotificationAsync(_stepCalorieChar);
                    _stepCalorieChar.ValueUpdated += StepCalorieChar_ValueUpdated;
                    Console.WriteLine("步数/卡路里数据同步已启动!");
                }
            }
        }

        /// <summary>
        /// 解析标准心率数据(蓝牙 SIG 协议)
        /// </summary>
        private int ParseHeartRate(byte[] data)
        {
            if (data.Length < 2) return 0;
            // 标志位:bit0 表示心率值长度(0=8位,1=16位)
            bool is16Bit = (data[0] & 0x01) != 0;
            return is16Bit ? BitConverter.ToUInt16(data, 1) : data[1];
        }

        /// <summary>
        /// 解析步数/卡路里数据(自定义协议)
        /// </summary>
        private (int stepCount, float calorie) ParseStepCalorie(byte[] data)
        {
            if (data.Length < 4) return (0, 0);
            // 协议:字节0-1=步数(小端序),字节2-3=卡路里(小端序,单位:0.1千卡)
            int stepCount = BitConverter.ToUInt16(data, 0);
            float calorie = BitConverter.ToUInt16(data, 2) / 10.0f;
            return (stepCount, calorie);
        }

        /// <summary>
        /// 导出数据到 Excel
        /// </summary>
        public void ExportToExcel(string filePath)
        {
            IWorkbook workbook = new XSSFWorkbook();
            ISheet sheet = workbook.CreateSheet("穿戴设备数据");

            // 表头
            IRow header = sheet.CreateRow(0);
            header.CreateCell(0).SetCellValue("同步时间");
            header.CreateCell(1).SetCellValue("心率(次/分钟)");
            header.CreateCell(2).SetCellValue("步数(步)");
            header.CreateCell(3).SetCellValue("卡路里(千卡)");

            // 填充数据
            for (int i = 0; i < _syncData.Count; i++)
            {
                IRow row = sheet.CreateRow(i + 1);
                var data = _syncData[i];
                row.CreateCell(0).SetCellValue(data.SyncTime.ToString("yyyy-MM-dd HH:mm:ss"));
                row.CreateCell(1).SetCellValue(data.HeartRate);
                row.CreateCell(2).SetCellValue(data.StepCount);
                row.CreateCell(3).SetCellValue(data.Calorie);
            }

            // 保存文件
            using (FileStream fs = new FileStream(filePath, FileMode.Create))
            {
                workbook.Write(fs);
            }
            workbook.Close();
            Console.WriteLine($"数据已导出到:{filePath}");
        }

        // 事件回调、启用通知等方法参考前文
        private async Task EnableNotificationAsync(ICharacteristic characteristic)
        {
            var descriptor = await characteristic.GetDescriptorAsync(Guid.Parse("00002902-0000-1000-8000-00805f9b34fb"));
            await descriptor.WriteAsync(new byte[] { 0x01, 0x00 });
        }

        private void HeartRateChar_ValueUpdated(object sender, CharacteristicUpdatedEventArgs e)
        {
            int heartRate = ParseHeartRate(e.Characteristic.Value);
            if (heartRate > 0)
            {
                var latestData = _syncData.LastOrDefault() ?? new WearableData();
                var newData = new WearableData
                {
                    SyncTime = DateTime.Now,
                    HeartRate = heartRate,
                    StepCount = latestData.StepCount,
                    Calorie = latestData.Calorie
                };
                _syncData.Add(newData);
                Console.WriteLine($"[{newData.SyncTime:HH:mm:ss}] 心率:{heartRate} 次/分钟");
            }
        }

        private void StepCalorieChar_ValueUpdated(object sender, CharacteristicUpdatedEventArgs e)
        {
            var (stepCount, calorie) = ParseStepCalorie(e.Characteristic.Value);
            if (stepCount > 0)
            {
                var latestData = _syncData.LastOrDefault() ?? new WearableData();
                var newData = new WearableData
                {
                    SyncTime = DateTime.Now,
                    HeartRate = latestData.HeartRate,
                    StepCount = stepCount,
                    Calorie = calorie
                };
                _syncData.Add(newData);
                Console.WriteLine($"[{newData.SyncTime:HH:mm:ss}] 步数:{stepCount} 步,卡路里:{calorie:F1} 千卡");
            }
        }

        // 调用示例
        static async Task Main(string[] args)
        {
            var demo = new WearableSyncDemo();
            await demo.ConnectWearableAsync();

            Console.WriteLine("数据同步中,按任意键导出并退出...");
            Console.ReadKey();
            demo.ExportToExcel($"wearable_data_{DateTime.Now:yyyyMMddHHmmss}.xlsx");
        }
    }
}

3. 实战三:蓝牙硬件控制(经典蓝牙 + BLE 双模式)

场景需求:

开发一个通用蓝牙控制工具,支持通过经典蓝牙(RFCOMM)或 BLE 控制智能灯,实现开关、亮度调节功能。

核心代码实现:

using System;
using System.Threading.Tasks;
using InTheHand.Net.Bluetooth;
using InTheHand.Net.Sockets;
using Plugin.BLE;
using Plugin.BLE.Abstractions.Contracts;

namespace BluetoothDeviceControl
{
    // 蓝牙控制模式枚举
    public enum BluetoothMode { Classic, BLE }

    class DeviceController
    {
        // 经典蓝牙客户端
        private readonly BluetoothClient _classicClient = new BluetoothClient();
        // BLE 适配器
        private readonly IAdapter _bleAdapter = CrossBluetoothLE.Current.Adapter;
        // 连接状态
        private bool _isConnected = false;
        private BluetoothMode _currentMode;
        // BLE 特征值
        private ICharacteristic _controlChar;

        // 设备配置
        private const string ClassicDeviceName = "SmartLight-RFCOMM";
        private const string BLEDeviceName = "SmartLight-BLE";
        private readonly Guid _bleServiceUuid = Guid.Parse("0000ffb0-0000-1000-8000-00805f9b34fb");
        private readonly Guid _bleCharUuid = Guid.Parse("0000ffb1-0000-1000-8000-00805f9b34fb");

        /// <summary>
        /// 连接设备(支持经典蓝牙/BLE)
        /// </summary>
        public async Task<bool> ConnectAsync(BluetoothMode mode)
        {
            _currentMode = mode;
            try
            {
                if (mode == BluetoothMode.Classic)
                {
                    // 经典蓝牙连接
                    var radio = BluetoothRadio.Default;
                    if (radio == null || radio.State != RadioState.On)
                    {
                        Console.WriteLine("蓝牙未开启!");
                        return false;
                    }

                    var devices = _classicClient.DiscoverDevices(10);
                    var targetDevice = devices.FirstOrDefault(d => d.DeviceName == ClassicDeviceName);
                    if (targetDevice == null)
                    {
                        Console.WriteLine("未找到经典蓝牙设备!");
                        return false;
                    }

                    _classicClient.Connect(targetDevice.DeviceAddress, BluetoothService.SerialPort);
                    _isConnected = true;
                    Console.WriteLine("经典蓝牙设备连接成功!");
                    return true;
                }
                else
                {
                    // BLE 连接
                    if (!CrossBluetoothLE.Current.IsOn)
                    {
                        Console.WriteLine("蓝牙未开启!");
                        return false;
                    }

                    IDevice targetDevice = null;
                    _bleAdapter.DeviceDiscovered += (s, e) =>
                    {
                        if (e.Device.Name == BLEDeviceName)
                        {
                            targetDevice = e.Device;
                        }
                    };

                    await _bleAdapter.StartScanningForDevicesAsync();
                    await Task.Delay(5000);
                    await _bleAdapter.StopScanningForDevicesAsync();

                    if (targetDevice == null)
                    {
                        Console.WriteLine("未找到 BLE 设备!");
                        return false;
                    }

                    await _bleAdapter.ConnectToDeviceAsync(targetDevice);
                    var service = await targetDevice.GetServiceAsync(_bleServiceUuid);
                    _controlChar = await service.GetCharacteristicAsync(_bleCharUuid);
                    _isConnected = true;
                    Console.WriteLine("BLE 设备连接成功!");
                    return true;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"连接失败:{ex.Message}");
                return false;
            }
        }

        /// <summary>
        /// 控制智能灯(开关/亮度)
        /// </summary>
        /// <param name="command">命令:ON/OFF/BRIGHT:50(亮度50%)</param>
        public async Task SendControlCommandAsync(string command)
        {
            if (!_isConnected)
            {
                Console.WriteLine("未连接设备!");
                return;
            }

            try
            {
                byte[] commandBytes = System.Text.Encoding.UTF8.GetBytes(command + "
");
                if (_currentMode == BluetoothMode.Classic)
                {
                    // 经典蓝牙发送
                    var stream = _classicClient.GetStream();
                    stream.Write(commandBytes, 0, commandBytes.Length);
                    stream.Flush();
                }
                else
                {
                    // BLE 发送
                    await _controlChar.WriteAsync(commandBytes);
                }
                Console.WriteLine($"发送控制命令:{command}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"发送命令失败:{ex.Message}");
            }
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        public async Task DisconnectAsync()
        {
            _isConnected = false;
            if (_currentMode == BluetoothMode.Classic)
            {
                _classicClient.Close();
            }
            else
            {
                if (_controlChar != null)
                {
                    await _controlChar.StopUpdatesAsync();
                }
                await _bleAdapter.DisconnectDeviceAsync(_bleAdapter.ConnectedDevices.FirstOrDefault());
            }
            Console.WriteLine("设备已断开连接!");
        }

        // 调用示例
        static async Task Main(string[] args)
        {
            var controller = new DeviceController();
            // 选择 BLE 模式连接
            if (await controller.ConnectAsync(BluetoothMode.BLE))
            {
                // 发送控制命令
                await controller.SendControlCommandAsync("ON"); // 开灯
                await Task.Delay(3000);
                await controller.SendControlCommandAsync("BRIGHT:70"); // 亮度70%
                await Task.Delay(3000);
                await controller.SendControlCommandAsync("OFF"); // 关灯

                // 断开连接
                await controller.DisconnectAsync();
            }
        }
    }
}

四、核心问题与解决方案

1. 设备扫描不到

原因:蓝牙适配器不支持对应协议(如 BLE 适配器扫描不到经典蓝牙设备)、设备未进入配对模式、系统权限不足;解决方案:
确认蓝牙适配器类型(经典蓝牙/BLE);设备进入配对模式(如 HC-05 长按按键至指示灯快闪);Windows 开启蓝牙服务(
services.msc
→ 启动「Bluetooth Support Service」);Linux 授予蓝牙权限(
sudo usermod -aG bluetooth $USER
)。

2. 数据传输乱码

原因:经典蓝牙波特率不匹配、数据编码格式不一致(UTF-8/GBK)、BLE 数据解析格式错误;解决方案:
经典蓝牙统一波特率(如 9600、115200);统一编码格式(优先 UTF-8);BLE 按设备协议解析字节数据(确认端序、字段长度)。

3. 连接不稳定(频繁断开)

原因:设备距离过远、干扰严重、低功耗设备自动休眠、蓝牙适配器驱动异常;解决方案:
缩短设备距离(建议 10 米内);避开 Wi-Fi 路由器、微波炉等干扰源;BLE 设备发送心跳包(每 10 秒发送一次空指令);更新蓝牙适配器驱动。

五、总结

C# 蓝牙开发的精髓在于「吃透协议+选对库+落地实战」:

基础层:掌握经典蓝牙的「串口流读写」和 BLE 的「GATT 层级操作」,这是所有蓝牙应用的基石;协议层:理解 RFCOMM 的「串口仿真」和 GATT 的「服务-特征值」模型,解决数据粘包、解析等核心问题;实战层:结合具体场景(传感器采集、穿戴同步、硬件控制),灵活运用不同协议和库,关注稳定性和用户体验。

本文覆盖的基础语法、协议解析和实战项目,可直接应用于大多数蓝牙场景。后续可进一步探索 BLE 加密通信、DFU 固件升级、跨平台适配等进阶内容,打造更强大的蓝牙应用。

参考资源

32feet.NET 官方文档:https://32feet.codeplex.com/Plugin.BLE 官方文档:https://github.com/xabre/xamarin-bluetooth-le蓝牙 SIG 协议规范:https://www.bluetooth.com/specifications/specs/NPOI 文档(Excel 导出):https://github.com/nissl-lab/npoi

© 版权声明

相关文章

暂无评论

none
暂无评论...