前言
蓝牙技术作为短距离无线通信的核心方案,已广泛应用于智能硬件、工业控制、穿戴设备等场景。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 稳定,文档丰富 | |
| Plugin.BLE | Windows、Android、iOS、Linux | BLE 为主 | 跨平台支持完善,GATT 操作简洁 | |
| InTheHand.Net.Bluetooth | Windows 10+/IoT Core | 经典蓝牙、BLE | 32feet.NET 升级版,支持 BLE 高级特性 | |
选型建议:快速开发 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);自定义服务:厂商定义(128 位 UUID,如
0000180d-...);
0000ffe0-...
特征值(Characteristic):服务下的具体数据项(如心率值、控制指令),支持 5 种操作:
| 操作类型 | 作用 | 示例场景 |
|---|---|---|
| Read(读取) | 客户端主动获取数据 | 查询设备电量 |
| Write(写入带响应) | 客户端发送数据,设备返回确认 | 发送控制指令(确保送达) |
| Write Without Response | 客户端发送数据,设备不返回确认 | 批量推送数据(速度快) |
| Notify(通知) | 设备主动推送数据,无需客户端确认 | 心率实时更新 |
| Indicate(指示) | 设备主动推送数据,需客户端确认 | 重要告警信息 |
描述符(Descriptor):特征值的补充信息,常用「客户端特征配置描述符」(UUID:),用于启用通知/指示。
00002902-...
(2)GATT 协议实战技巧
启用通知:需向 描述符写入
00002902-...(启用通知)或
0x0001(启用指示);MTU 协商:BLE 最大传输单元(MTU)默认 23 字节,协商后可提升至 517 字节,减少分包传输;数据解析:BLE 设备数据多为小端序(Little-Endian),C# 中需用
0x0002 解析(小端序)。
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 开启蓝牙服务( → 启动「Bluetooth Support Service」);Linux 授予蓝牙权限(
services.msc)。
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
