# 实战C# BLE设备交互:从扫描到数据收发的完整WinForm案例解析
在物联网和智能硬件快速发展的今天,低功耗蓝牙(BLE)技术因其低能耗、低成本的特点,成为连接智能设备的首选方案之一。作为一名C#开发者,掌握BLE设备交互技术能够为体重秤、健康监测设备等物联网产品开发提供强大支持。本文将带你深入WinForm环境下BLE开发的完整流程,从设备扫描到数据收发,构建一个可直接复用的通信框架。
## 1. 环境准备与项目配置
开发BLE应用首先需要确保环境配置正确。与传统的蓝牙开发不同,BLE开发需要特定的Windows API支持。以下是关键配置步骤:
1. **开发环境要求**:
- Visual Studio 2019或更高版本
- Windows 10 1809及以上版本(支持完整BLE API)
- 支持蓝牙4.0以上的硬件适配器
2. **NuGet包引用**:
在解决方案资源管理器中右键项目,选择"管理NuGet程序包",搜索并安装以下包:
```bash
Microsoft.Windows.SDK.Contracts
```
这个包提供了访问Windows运行时API的能力,包括BLE相关功能。相比直接修改.csproj文件添加平台版本,这种方式更加稳定可靠。
3. **项目目标平台**:
确保项目目标平台版本设置为10.0或更高。在解决方案资源管理器中右键项目,选择"属性",在"应用程序"标签页中设置目标平台:
```xml
<TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
```
> 提示:如果遇到System.Runtime.dll引用错误或IDisposable未实现等问题,通常是由于平台版本不匹配导致的,使用NuGet方式引用可以避免这些问题。
## 2. BLE核心功能实现
### 2.1 设备扫描与发现
BLE设备交互的第一步是发现周围的可用设备。我们使用`BluetoothLEAdvertisementWatcher`类来实现设备扫描:
```csharp
public class BleCore
{
private BluetoothLEAdvertisementWatcher deviceWatcher;
private List<BluetoothLEDevice> deviceList = new List<BluetoothLEDevice>();
public void StartBleDeviceWatcher()
{
deviceWatcher = new BluetoothLEAdvertisementWatcher
{
ScanningMode = BluetoothLEScanningMode.Active
};
// 设置信号强度过滤
deviceWatcher.SignalStrengthFilter.InRangeThresholdInDBm = -80;
deviceWatcher.SignalStrengthFilter.OutOfRangeThresholdInDBm = -90;
deviceWatcher.SignalStrengthFilter.OutOfRangeTimeout = TimeSpan.FromMilliseconds(5000);
deviceWatcher.SignalStrengthFilter.SamplingInterval = TimeSpan.FromMilliseconds(2000);
deviceWatcher.Received += DeviceWatcher_Received;
deviceWatcher.Start();
}
private void DeviceWatcher_Received(BluetoothLEAdvertisementWatcher sender,
BluetoothLEAdvertisementReceivedEventArgs args)
{
BluetoothLEDevice.FromBluetoothAddressAsync(args.BluetoothAddress).Completed = (asyncInfo, asyncStatus) =>
{
if (asyncStatus == AsyncStatus.Completed && asyncInfo.GetResults() != null)
{
var currentDevice = asyncInfo.GetResults();
if (!deviceList.Any(d => d.DeviceId == currentDevice.DeviceId))
{
deviceList.Add(currentDevice);
// 触发设备发现事件
DeviceDiscovered?.Invoke(this, new DeviceDiscoveredEventArgs(currentDevice));
}
}
};
}
}
```
这段代码实现了BLE设备的主动扫描,并通过信号强度过滤优化了设备发现过程。`BluetoothLEScanningMode.Active`表示主动扫描模式,可以获取更多设备信息但功耗略高。
### 2.2 设备连接与服务发现
发现设备后,下一步是连接设备并发现其提供的服务。BLE设备通过GATT(通用属性)协议组织功能,每个设备包含多个服务,每个服务又包含多个特征值(Characteristic):
```csharp
public async Task ConnectToDeviceAsync(BluetoothLEDevice device)
{
CurrentDevice = device;
CurrentDevice.ConnectionStatusChanged += Device_ConnectionStatusChanged;
// 获取设备服务
GattDeviceServicesResult result = await CurrentDevice.GetGattServicesAsync();
if (result.Status == GattCommunicationStatus.Success)
{
foreach (var service in result.Services)
{
// 发现服务特征
await DiscoverCharacteristicsAsync(service);
}
}
}
private async Task DiscoverCharacteristicsAsync(GattDeviceService service)
{
GattCharacteristicsResult result = await service.GetCharacteristicsAsync();
if (result.Status == GattCommunicationStatus.Success)
{
foreach (var characteristic in result.Characteristics)
{
// 根据特征属性处理不同类型的特征
ProcessCharacteristic(characteristic);
}
}
}
```
### 2.3 特征值处理与数据交互
BLE设备的功能通过特征值(Characteristic)暴露给客户端。特征值有不同的属性,我们需要根据属性进行相应处理:
| 特征属性 | 说明 | 典型用途 |
|---------|------|---------|
| Read | 可读 | 读取传感器数据 |
| Write | 可写 | 发送控制命令 |
| Notify | 通知 | 接收实时数据更新 |
| Indicate| 指示 | 可靠的数据通知 |
```csharp
private void ProcessCharacteristic(GattCharacteristic characteristic)
{
// 位运算判断特征属性
if (characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Read))
{
// 处理可读特征
ReadableCharacteristics.Add(characteristic);
}
if (characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Write))
{
// 处理可写特征
WritableCharacteristics.Add(characteristic);
}
if (characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Notify))
{
// 启用通知
EnableNotifications(characteristic);
}
}
private async void EnableNotifications(GattCharacteristic characteristic)
{
// 设置通知类型
GattCommunicationStatus status = await characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
GattClientCharacteristicConfigurationDescriptorValue.Notify);
if (status == GattCommunicationStatus.Success)
{
characteristic.ValueChanged += Characteristic_ValueChanged;
}
}
private void Characteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
{
// 处理接收到的数据
CryptographicBuffer.CopyToByteArray(args.CharacteristicValue, out byte[] data);
DataReceived?.Invoke(this, new DataReceivedEventArgs(data));
}
```
## 3. WinForm界面集成与实战案例
### 3.1 体重秤数据采集UI实现
将BLE核心功能集成到WinForm应用中,我们可以构建一个完整的体重秤数据采集界面:
```csharp
public partial class MainForm : Form
{
private readonly BleManager bleManager = new BleManager();
public MainForm()
{
InitializeComponent();
bleManager.DeviceDiscovered += BleManager_DeviceDiscovered;
bleManager.DataReceived += BleManager_DataReceived;
}
private void ScanButton_Click(object sender, EventArgs e)
{
deviceListBox.Items.Clear();
bleManager.StartScanning();
}
private void BleManager_DeviceDiscovered(object sender, DeviceDiscoveredEventArgs e)
{
if (InvokeRequired)
{
Invoke(new Action(() => BleManager_DeviceDiscovered(sender, e)));
return;
}
deviceListBox.Items.Add($"{e.Device.Name} ({e.Device.DeviceId})");
}
private void BleManager_DataReceived(object sender, DataReceivedEventArgs e)
{
if (InvokeRequired)
{
Invoke(new Action(() => BleManager_DataReceived(sender, e)));
return;
}
// 解析体重数据(假设数据格式为小端序,单位kg)
float weight = BitConverter.ToSingle(e.Data, 0);
weightLabel.Text = $"{weight:F2} kg";
// 更新历史记录
logListBox.Items.Add($"{DateTime.Now:T} - 体重: {weight:F2}kg");
}
private async void ConnectButton_Click(object sender, EventArgs e)
{
if (deviceListBox.SelectedIndex >= 0)
{
await bleManager.ConnectToDeviceAsync(deviceListBox.SelectedIndex);
MessageBox.Show("连接成功", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
```
### 3.2 数据解析与处理
不同BLE设备的数据格式各异。以常见的体重秤为例,数据通常以小端序存储,需要正确解析:
```csharp
public static float ParseWeightData(byte[] data)
{
// 假设数据格式:
// 字节0-1:体重值(小端序,单位0.01kg)
// 字节2:标志位
// 字节3-4:时间戳
if (data.Length < 5)
throw new ArgumentException("无效的数据长度");
// 小端序转换
ushort weightValue = (ushort)(data[1] << 8 | data[0]);
float weight = weightValue / 100f; // 转换为kg
// 检查标志位判断数据有效性
bool isValid = (data[2] & 0x01) == 0x01;
return isValid ? weight : float.NaN;
}
```
## 4. 高级功能与优化
### 4.1 异步操作与线程安全
BLE操作大多是异步的,正确处理异步回调对应用的稳定性至关重要:
```csharp
public async Task WriteDataAsync(byte[] data)
{
if (CurrentWriteCharacteristic == null)
throw new InvalidOperationException("未设置写特征");
try
{
var buffer = CryptographicBuffer.CreateFromByteArray(data);
GattCommunicationStatus status = await CurrentWriteCharacteristic
.WriteValueWithResultAsync(buffer)
.AsTask()
.ConfigureAwait(false);
if (status != GattCommunicationStatus.Success)
{
throw new IOException($"写入失败,状态: {status}");
}
}
catch (Exception ex)
{
// 处理异常,如重试或通知用户
LogError(ex);
throw;
}
}
```
### 4.2 连接管理与错误处理
BLE连接可能因各种原因中断,实现健壮的重连机制很重要:
```csharp
private async void HandleDisconnection()
{
while (true)
{
if (CurrentDevice?.ConnectionStatus == BluetoothConnectionStatus.Disconnected)
{
try
{
// 等待5秒后尝试重连
await Task.Delay(5000);
await ReconnectAsync();
}
catch
{
// 重连失败,继续等待
}
}
else
{
await Task.Delay(1000);
}
}
}
private async Task ReconnectAsync()
{
if (string.IsNullOrEmpty(CurrentDeviceMac))
return;
CurrentDevice?.Dispose();
CurrentDevice = await BluetoothLEDevice.FromBluetoothAddressAsync(
BluetoothAddress.Parse(CurrentDeviceMac));
if (CurrentDevice != null)
{
await ConnectToDeviceAsync(CurrentDevice);
}
}
```
### 4.3 性能优化与功耗管理
对于需要长时间运行的BLE应用,功耗管理尤为重要:
1. **扫描优化**:
```csharp
// 适当调整扫描参数平衡发现速度与功耗
deviceWatcher.SignalStrengthFilter.InRangeThresholdInDBm = -70;
deviceWatcher.SignalStrengthFilter.OutOfRangeThresholdInDBm = -85;
deviceWatcher.SignalStrengthFilter.SamplingInterval = TimeSpan.FromMilliseconds(2000);
```
2. **连接参数更新**:
```csharp
// 请求更长的连接间隔以降低功耗
var parameters = new BluetoothLEConnectionParameters
{
ConnectionInterval = 45, // 单位1.25ms
Latency = 0,
LinkTimeout = 400 // 单位10ms
};
await CurrentDevice.RequestConnectionParametersAsync(parameters);
```
3. **资源释放**:
```csharp
public void Dispose()
{
deviceWatcher?.Stop();
deviceWatcher = null;
CurrentNotifyCharacteristic?.ValueChanged -= Characteristic_ValueChanged;
CurrentDevice?.Dispose();
CurrentDevice = null;
}
```
## 5. 调试技巧与常见问题解决
开发BLE应用时,掌握有效的调试方法能显著提高效率:
**常见问题及解决方案**:
| 问题现象 | 可能原因 | 解决方案 |
|---------|---------|---------|
| 扫描不到设备 | 设备不可发现 | 确保设备处于可发现模式 |
| 连接失败 | 设备已连接其他主机 | 断开其他连接后再试 |
| 数据收发异常 | 特征属性不匹配 | 检查特征属性是否支持所需操作 |
| 连接不稳定 | 信号弱或干扰 | 缩短距离或减少障碍物 |
| 通知不工作 | 未正确配置通知 | 调用WriteClientCharacteristicConfigurationDescriptorAsync |
**调试工具推荐**:
1. **Bluetooth LE Explorer**:微软官方工具,可查看设备服务与特征
2. **Wireshark**:配合蓝牙适配器抓包分析
3. **nRF Connect**:功能强大的BLE调试工具
**日志记录建议**:
```csharp
public void LogCommunication(string message)
{
string logEntry = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} - {message}";
// 写入文件
File.AppendAllText("ble_communication.log", logEntry + Environment.NewLine);
// 显示在UI(可选)
if (logTextBox.InvokeRequired)
{
logTextBox.Invoke(new Action(() => logTextBox.AppendText(logEntry + Environment.NewLine)));
}
else
{
logTextBox.AppendText(logEntry + Environment.NewLine);
}
}
```
通过本文的完整案例,我们构建了一个可复用的C# BLE通信框架,涵盖了从设备发现到数据收发的全流程。在实际项目中,可根据具体设备特性调整数据解析逻辑和UI展示方式。