在开发MAUI应用程序时,蓝牙协议的应用是一个重要的环节,尤其是在需要与外部设备如智能跳绳进行数据交换的场景中。以下是我在开发过程中的一些经验和心得,希望能为你的项目提供帮助。
1. 蓝牙协议基础
蓝牙协议是无线通信的一种标准,允许设备之间进行短距离的数据交换。在MAUI开发中,我们主要关注的是BLE(Bluetooth Low Energy,低功耗蓝牙)协议,它以其低功耗和低成本的特点被广泛应用于智能设备中。
2. 使用Ble蓝牙助手
在开发过程中,我使用了“BLE蓝牙助手”这款应用来辅助调试和理解蓝牙协议。这款应用可以帮助我们扫描周围的BLE设备,查看设备的信号强度(RSSI),连接设备,并查看服务和特征。通过这个工具,我们可以更直观地理解BLE协议的工作原理和数据交换过程。
3. MAUI中的蓝牙开发
MAUI(.NET Multi-platform App UI)是一个跨平台框架,它允许开发者使用C#和XAML创建跨平台的移动和桌面应用。MAUI以其性能优异、可扩展性强和结构简单而受到开发者的青睐。在本次开发中,MAUI作为主要的开发框架,提供了丰富的控件和API,使得与蓝牙设备的对接成为可能。
MAUI支持多种平台,包括Android、iOS、macOS和Windows,这为开发跨平台应用提供了极大的便利。在本项目中,我们将重点利用MAUI的跨平台特性,开发一款能够在不同操作系统上运行的跳绳计数应用。
在MAUI中,我们可以通过Plugin.BLE
来实现蓝牙功能。以下是一些关键步骤:
3.1 扫描设备
首先,我们需要扫描周围的BLE设备。在MAUI中,我们可以使用以下代码来启动扫描:
await CurrentAdapter.StartScanningForDevicesAsync();
public async Task<bool> StartScanAsync()
{
//检查获取蓝牙权限
bool isPermissionPass = await CheckAndRequestBluetoothPermission();
if (!isPermissionPass)
return false;
// 在使用之前,确保 _scanForAedCts 已经被实例化
ListDevice.Clear();
try
{
if(CurrentAdapter == null)
{
CurrentAdapter = CrossBluetoothLE.Current.Adapter;
}
await CurrentAdapter.StopScanningForDevicesAsync();
CurrentAdapter.DeviceDiscovered += Adapter_DeviceDiscovered;
CurrentAdapter.ScanTimeoutElapsed += Adapter_ScanTimeoutElapsed;
//蓝牙扫描时间
CurrentAdapter.ScanTimeout = 30 * 1000;
//默认LowPower
CurrentAdapter.ScanMode = Plugin.BLE.Abstractions.Contracts.ScanMode.LowPower;
Debug.WriteLine($"开始扫描外设, IsAvailable={CrossBluetoothLE.Current.IsAvailable}, IsOn={CrossBluetoothLE.Current.IsOn}, State={CrossBluetoothLE.Current.State}, ScanMode={CurrentAdapter.ScanMode}, ScanTimeout={CurrentAdapter.ScanTimeout}");
await CurrentAdapter.StartScanningForDevicesAsync(cancellationToken: _scanForAedCts.Token);
Debug.WriteLine($"结束扫描外设");
}
catch (OperationCanceledException)
{
Debug.WriteLine($"扫描外设任务取消");
}
catch (Exception ex)
{
Debug.WriteLine($"扫描外设出错, {ex.Message}");
}
finally
{
CurrentAdapter.DeviceDiscovered -= Adapter_DeviceDiscovered;
CurrentAdapter.ScanTimeoutElapsed -= Adapter_ScanTimeoutElapsed;
//_scanForAedCts.Dispose();
}
return true;
}
在扫描过程中,我们可以通过DeviceDiscovered
事件来获取发现的设备信息。
3.2 连接设备
一旦找到目标设备,我们就可以建立连接。在MAUI中,连接设备的过程如下:
await CurrentAdapter.ConnectToDeviceAsync(device, new ConnectParameters(false, true));
/// <summary>
/// 连接设备
/// </summary>
/// <param name="uuid"></param>
/// <returns></returns>
public async Task<IDevice?> ConnectDeviceAsync(Guid uuid)
{
try
{
if (CurrentAdapter == null)
{
CurrentAdapter = CrossBluetoothLE.Current.Adapter;
}
var connectedDevices = CurrentAdapter.ConnectedDevices;
if (connectedDevices.Count > 0)
{
// 至少有一个设备已经连接
foreach (var device in connectedDevices)
{
// 可以在这里处理每个已连接的设备
if (device.Id == uuid)
{
await StartNotify(device);
return device;
}
Console.WriteLine(device.Name);
}
}
else
{
try
{
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15)))
{
var device = await CurrentAdapter.ConnectToKnownDeviceAsync(uuid, default(ConnectParameters), cts.Token);
await StartNotify(device);
return device;
}
}
catch (Exception ex) { Debug.WriteLine($"蓝牙连接设备失败, {ex.Message}"); }
}
}
catch(Exception ex)
{
Debug.WriteLine($"蓝牙连接设备失败, {ex.Message}");
}
return null;
}
这里device
是我们通过扫描得到的设备对象,ConnectParameters
用于设置连接参数。
3.3 获取服务和特征
连接成功后,我们可以获取设备提供的服务和特征,这对于数据交换至关重要:
var services = await device.GetGattServicesAsync();
3.4 数据读写
通过获取的特征,我们可以进行数据的读写操作。例如,读取跳绳的次数和时间:
var characteristic = services.First().GetCharacteristics().First();
var readResult = await characteristic.ReadValueAsync();
/// <summary>
/// 读取数据
/// </summary>
/// <param name="characteristic"></param>
/// <returns></returns>
public async Task<byte[]?> ReadDataAsync(ICharacteristic characteristic)
{
//根据Plugin.BLE要求,在主线程读写数据
var result = await MainThread.InvokeOnMainThreadAsync(async () =>
{
try
{
//读取数据
var ary = await characteristic.ReadAsync();
Debug.WriteLine($"读取成功,长度={ary.data.Length}");
return (ary.data);
}
catch (Exception ex)
{
Debug.WriteLine($"读取错误, 目标设备蓝牙连接状态={BleDevice?.State}, {ex.Message}");
return null;
}
});
return result;
}
/// <summary>
/// 读取蓝牙设备数据
/// </summary>
/// <param name="guid"></param>
/// <returns></returns>
public async Task<byte[]?> ReadDataAsync(IDevice device)
{
var service = await device.GetServiceAsync(new Guid("0000180D-0000-1000-8000-00805F9B34FB"));
if (service != null)
{
var characteristic = await service.GetCharacteristicAsync(new Guid("00002A37-0000-1000-8000-00805F9B34FB"));
if (characteristic != null)
{
var ary = await characteristic.ReadAsync();
return (ary.data);
}
}
return null;
}
/// <summary>
/// 写入蓝牙设备数据
/// </summary>
/// <param name="device"></param>
/// <param name="ary"></param>
/// <returns></returns>
public async Task<int> SendDataAsync(IDevice device, byte[] ary)
{
var service = await device.GetServiceAsync(_serviceUuid);
if (service != null)
{
var characteristic = await service.GetCharacteristicAsync(_characteristicUuid);
if (characteristic != null && characteristic.CanWrite==true)
{
return await characteristic.WriteAsync(ary);
}
}
return 0;
}
3.5事件订阅
通过获取的特征,我们可以进行数据的通知操作。
#region 订阅事件
private async Task<int> StartNotify(IDevice device)
{
try
{
var service = await device.GetServiceAsync(_serviceUuid);
if (service != null)
{
var notifyCharacteristic = await service.GetCharacteristicAsync(_characteristicUuid);
if (notifyCharacteristic.Properties.HasFlag(CharacteristicPropertyType.Notify))
{
// 特性支持通知
// 订阅事件
notifyCharacteristic.ValueUpdated += NotifyCharacteristic_ValueUpdated;
// 启用通知
await notifyCharacteristic.StartUpdatesAsync();
}
}
}
catch(Exception ex)
{
}
return 0;
}
// 处理特性值更新事件
private void NotifyCharacteristic_ValueUpdated(object sender, Plugin.BLE.Abstractions.EventArgs.CharacteristicUpdatedEventArgs e)
{
// 处理特性值更新
NotifyQueue.Enqueue( e.Characteristic.Value);
// ...
}
public async Task<byte[]?> ReadNotify(IDevice device)
{
if (device.IsConnectable == true)
{
if (NotifyQueue.Count > 0)
{
return NotifyQueue.Dequeue();
}
}
return null;
}
private async Task<int> StopNotify(IDevice device)
{
var service = await device.GetServiceAsync(_serviceUuid);
if (service != null)
{
// 获取服务和特性
var notifyCharacteristic = await service.GetCharacteristicAsync(_characteristicUuid);
if (notifyCharacteristic.Properties.HasFlag(CharacteristicPropertyType.Notify))
{
// 特性支持通知
// 订阅事件
notifyCharacteristic.ValueUpdated -= NotifyCharacteristic_ValueUpdated;
// 启用通知
await notifyCharacteristic.StopUpdatesAsync();
}
}
return 0;
}
#endregion
4. 跳绳设备对接实践
在实际对接跳绳设备时,我们需要根据设备的技术文档来确定服务和特征的UUID。一旦确定,就可以按照上述步骤进行连接和数据交换。例如,读取跳绳次数的特征可能有一个特定的UUID,我们可以通过这个UUID来读取或写入数据。
5. 注意事项
- 确保在开发过程中,手机的蓝牙功能处于开启状态。
- 在配对设备时,确保手机与跳绳设备的距离足够近,以保证信号的稳定性。
- 在读取和写入数据时,要注意数据格式和编码方式,确保数据的正确解析。
通过上述步骤和注意事项,可以在MAUI中顺利实现与BLE设备的对接,记录跳绳的次数与时间。希望这些经验能够帮助你在开发过程中少走弯路,快速实现功能