bh003- Blazor hybrid / Maui 使用蓝牙BLE快速教程
1. 建立工程 bh003_ble源码
2. 添加 nuget 包
BlazorHybrid.Maui.Permissions 因为源码比较长,主要是一些检查和申请权限,BLE权限相关代码,就不占用篇幅列出,感兴趣的同学直接打开源码参考
顺便打开可空 enable
3. 添加蓝牙权限
安卓
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
</manifest>iOS
Info.plist
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
<string>bluetooth-peripheral</string>
</array>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>此应用程序需要访问您的蓝牙。请根据要求授予权限.</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>此应用程序需要访问您的蓝牙。请根据要求授予权限.</string>以下是完整文件
LSRequiresIPhoneOS UIDeviceFamily 1 2 UIRequiredDeviceCapabilities arm64 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight XSAppIconAssets Assets.xcassets/appicon.appiconset <key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
<string>bluetooth-peripheral</string>
</array>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>此应用程序需要访问您的蓝牙。请根据要求授予权限.</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>此应用程序需要访问您的蓝牙。请根据要求授予权限.</string>Windows
Package.appxmanifest
4. 编辑 Index.html 文件,引用 BootstrapBlazor UI 库.
完整文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<title>bh003_ble</title>
<base href="/" />
<link href="_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css" rel="stylesheet">
<link href="_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css" rel="stylesheet">
<link href="_content/BootstrapBlazor/css/motronic.min.css" rel="stylesheet">
<link href="css/app.css" rel="stylesheet" />
<link href="bh003_ble.styles.css" rel="stylesheet" />
</head>
<body>
Loading...
An unhandled error has occurred.
<a target="_blank" href="https://www.cnblogs.com/" >Reload</a>
<a ><i ></i></a>
</body>
</html>
5. 添加 BootstrapBlazorRoot 组件
Main.razor 文件添加 BootstrapBlazorRoot 组件
6. 添加命名空间引用
_Imports.razor
@using BootstrapBlazor.Components7. 添加服务
MauiProgram.cs
添加
builder.Services.AddDensenExtensions();
builder.Services.ConfigureJsonLocalizationOptions(op =>
{
// 忽略文化信息丢失日志
op.IgnoreLocalizerMissing = true;
});
builder.Services.AddSingleton<BluetoothLEServices>();
builder.Services.AddScoped<IStorage, StorageService>();完整文件
using bh003_ble.Data;using Microsoft.Extensions.Logging;using BlazorHybrid.Maui.Shared;using BootstrapBlazor.WebAPI.Services;namespace bh003_ble{ public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); }); builder.Services.AddMauiBlazorWebView();#if DEBUG builder.Services.AddBlazorWebViewDeveloperTools(); builder.Logging.AddDebug();#endif builder.Services.AddSingleton(); builder.Services.AddDensenExtensions();
builder.Services.ConfigureJsonLocalizationOptions(op =>
{
// 忽略文化信息丢失日志
op.IgnoreLocalizerMissing = true;
});
builder.Services.AddSingleton<BluetoothLEServices>();
builder.Services.AddScoped<IStorage, StorageService>(); return builder.Build(); } }}8. 添加代码后置文件 Pages/Index.razor.cs
Index.razor.cs
using BlazorHybrid.Core.Device;
using BlazorHybrid.Maui.Shared;
using BootstrapBlazor.Components;
using BootstrapBlazor.WebAPI.Services;
using Microsoft.AspNetCore.Components;
using System.Diagnostics.CodeAnalysis;
namespace bh003_ble.Pages;
public partial class Index : IAsyncDisposable
{
BluetoothLEServices? MyBleTester { get; set; }
protected IStorage? Storage { get; set; }
protected ToastService? ToastService { get; set; }
public void SetTagDeviceName(BleTagDevice ble)
{
MyBleTester.TagDevice = ble;
if (!isInit)
{
MyBleTester.OnMessage += OnMessage;
MyBleTester.OnDataReceived += OnDataReceived;
MyBleTester.OnStateConnect += OnStateConnect;
isInit = true;
}
}
public event Action<string>? OnMessage;
public event Action<string>? OnDataReceived;
public event Action<bool>? OnStateConnect;
bool isInit = false;
public async Task<List<BleDevice>?> StartScanAsync() => await MyBleTester.StartScanAsync();
public async Task<List<BleService>?> ConnectToKnownDeviceAsync(Guid deviceID, string? deviceName = null) => await MyBleTester.ConnectToKnownDeviceAsync(deviceID, deviceName);
public async Task<List<BleCharacteristic>?> GetCharacteristicsAsync(Guid serviceid) => await MyBleTester.GetCharacteristicsAsync(serviceid);
public async Task<string?> ReadDeviceName(Guid? serviceid, Guid? characteristic) => await MyBleTester.ReadDeviceName(serviceid, characteristic);
public async Task<byte[]?> ReadDataAsync(Guid characteristic) => await MyBleTester.ReadDataAsync(characteristic);
public async Task<bool> SendDataAsync(Guid characteristic, byte[] ary) => await MyBleTester.SendDataAsync(characteristic, ary);
public async Task<bool> DisConnectDeviceAsync() => await MyBleTester.DisConnectDeviceAsync();
public Task<bool> BluetoothIsBusy() => MyBleTester.BluetoothIsBusy();
private bool IsScanning = false;
private List<BleDevice>? Devices { get; set; }
private List<BleService>? Services { get; set; }
private List<BleCharacteristic>? Characteristics { get; set; }
private string? ReadResult { get; set; }
private string? Message { get; set; } = "";
BleTagDevice BleInfo { get; set; } = new BleTagDevice();
private List<SelectedItem> DemoList { get; set; } = new List<SelectedItem>() { new SelectedItem() { Text = "测试数据", Value = "" } };
private List<SelectedItem> DeviceList { get; set; } = new List<SelectedItem>();
private List<SelectedItem> ServiceidList { get; set; } = new List<SelectedItem>();
private List<SelectedItem> CharacteristicList { get; set; } = new List<SelectedItem>();
private Dictionary<string, object>? IsScanningCss => IsScanning ? new() { { "disabled", "" }, } : null;
bool IsAutoConnect { get; set; }
bool IsAuto { get; set; }
bool IsInit { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await Init();
}
}
async Task<bool> Init()
{
try
{
if (IsInit) return true;
if (await BluetoothIsBusy())
{
await ToastService.Warning("蓝牙正在使用中,请稍后再试");
return false;
}
OnMessage += Tools_OnMessage;
OnDataReceived += Tools_OnDataReceived;
OnStateConnect += Tools_OnStateConnect;
SetTagDeviceName(BleInfo);
IsInit = true;
StateHasChanged();
var deviceID = await Storage.GetValue("bleDeviceID", string.Empty);
if (!string.IsNullOrEmpty(deviceID))
{
BleInfo.Name = await Storage.GetValue("bleDeviceName", string.Empty);
BleInfo.DeviceID = Guid.Parse(deviceID);
var serviceid = await Storage.GetValue("bleServiceid", string.Empty);
if (!string.IsNullOrEmpty(serviceid)) BleInfo.Serviceid = Guid.Parse(serviceid);
var characteristic = await Storage.GetValue("bleCharacteristic", string.Empty);
if (!string.IsNullOrEmpty(characteristic)) BleInfo.Characteristic = Guid.Parse(characteristic);
var auto = await Storage.GetValue("bleAutoConnect", string.Empty);
if (auto == "True")
{
IsAuto = true;
await AutoRead();
}
}
return true;
}
catch (Exception ex)
{
System.Console.WriteLine(ex.Message);
}
return false;
}
private async Task AutoRead()
{
Services = null;
Characteristics = null;
Message = "";
ReadResult = "";
Devices = new List<BleDevice>() { new BleDevice() { Id = BleInfo.DeviceID, Name = BleInfo.Name } };
DeviceList = new List<SelectedItem>() { new SelectedItem() { Text = BleInfo.Name, Value = BleInfo.DeviceID.ToString() } };
IsAutoConnect = true;
await OnDeviceSelect();
IsAutoConnect = false;
}
private async Task OnStateChanged(bool value)
{
await Storage.SetValue("bleAutoConnect", value.ToString());
}
private void Tools_OnStateConnect(bool obj)
{
}
private async void Tools_OnDataReceived(string message)
{
ReadResult = message;
Tools_OnMessage(message);
await InvokeAsync(StateHasChanged);
}
private async void Tools_OnMessage(string message)
{
if (Message != null && Message.Length > 500) Message = Message.Substring(0, 500);
Message = $"{message}\r\n{Message}";
await InvokeAsync(StateHasChanged);
}
//扫描外设
private async void ScanDevice()
{
if (!await Init()) return;
IsScanning = true;
Devices = null;
Services = null;
Characteristics = null;
Message = "";
ReadResult = "";
DeviceList = new List<SelectedItem>() { new SelectedItem() { Text = "请选择...", Value = "" } };
//开始扫描
Devices = await StartScanAsync();
if (Devices != null)
{
Devices.ForEach(a => DeviceList.Add(new SelectedItem() { Active = IsAutoConnect && a.Id == BleInfo.DeviceID, Text = a.Name ?? a.Id.ToString(), Value = a.Id.ToString() }));
}
IsScanning = false;
//异步更新UI
await InvokeAsync(StateHasChanged);
}
//连接外设
private async Task OnDeviceSelect(SelectedItem item)
{
if (IsAutoConnect || item.Value == "") return;
BleInfo.Name = item.Text;
BleInfo.DeviceID = Guid.Parse(item.Value);
await OnDeviceSelect();
}
private async Task OnDisConnectDevice()
{
if (await DisConnectDeviceAsync())
await ToastService.Success("断开成功");
else
await ToastService.Error("断开失败");
}
private async Task OnDeviceSelect()
{
Services = null;
Characteristics = null;
Message = "";
ReadResult = "";
ServiceidList = new List<SelectedItem>() { new SelectedItem() { Text = "请选择...", Value = "" } };
//连接外设
Services = await ConnectToKnownDeviceAsync(BleInfo.DeviceID, BleInfo.Name);
if (Services != null)
{
Services.ForEach(a => ServiceidList.Add(new SelectedItem() { Active = IsAutoConnect && a.Id == BleInfo.Serviceid, Text = a.Name ?? a.Id.ToString(), Value = a.Id.ToString() }));
await Storage.SetValue("bleDeviceID", BleInfo.DeviceID.ToString());
await Storage.SetValue("bleDeviceName", BleInfo.Name ?? "上次设备");
if (BleInfo.Serviceid != Guid.Empty && IsAutoConnect)
{
await OnServiceidSelect();
}
}
//异步更新UI
await InvokeAsync(StateHasChanged);
}
private async Task OnServiceidSelect(SelectedItem item)
{
if (IsAutoConnect || item.Value == "") return;
BleInfo.Serviceid = Guid.Parse(item.Value);
await OnServiceidSelect();
}
private async Task OnServiceidSelect()
{
Characteristics = null;
Message = "";
ReadResult = "";
CharacteristicList = new List<SelectedItem>() { new SelectedItem() { Text = "请选择...", Value = "" } };
Characteristics = await GetCharacteristicsAsync(BleInfo.Serviceid);
if (Characteristics != null)
{
Characteristics.ForEach(a => CharacteristicList.Add(new SelectedItem() { Active = IsAutoConnect && a.Id == BleInfo.Characteristic, Text = a.Name ?? a.Id.ToString(), Value = a.Id.ToString() }));
await Storage.SetValue("bleServiceid", BleInfo.Serviceid.ToString());
if (BleInfo.Characteristic != Guid.Empty && IsAutoConnect)
{
await ReadDeviceName();
}
}
await InvokeAsync(StateHasChanged);
}
private async Task OnCharacteristSelect(SelectedItem item)
{
if (IsAutoConnect) return;
BleInfo.Characteristic = Guid.Parse(item.Value);
await ReadDeviceName();
}
//读取数值
private async Task ReadDeviceName()
{
Message = "";
//读取数值
ReadResult = await ReadDeviceName(BleInfo.Serviceid, BleInfo.Characteristic);
await Storage.SetValue("bleCharacteristic", BleInfo.Characteristic.ToString());
if (!string.IsNullOrEmpty(ReadResult)) await ToastService.Information("读取成功", ReadResult);
//异步更新UI
await InvokeAsync(StateHasChanged);
}
private async Task ReadDataAsync()
{
Message = "";
//读取数值
var res = await ReadDataAsync(BleInfo.Characteristic);
if (!string.IsNullOrEmpty(ReadResult)) await ToastService.Information("读取成功", res.ToString());
//异步更新UI
await InvokeAsync(StateHasChanged);
}
private async Task SendDataAsync()
{
Message = "";
//读取数值
var res = await SendDataAsync(BleInfo.Characteristic, null);
await ToastService.Information("成功发送", res.ToString());
//异步更新UI
await InvokeAsync(StateHasChanged);
}
ValueTask IAsyncDisposable.DisposeAsync()
{
OnMessage -= Tools_OnMessage;
OnDataReceived -= Tools_OnDataReceived;
OnStateConnect -= Tools_OnStateConnect;
return new ValueTask();
}
}9. 添加 UI Pages/Index.razor
Index.razor
@page "/"蓝牙
@if (Devices != null) {<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
</manifest>@if (Characteristics != null) {<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
</manifest> } } @if (Devices != null){<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
</manifest> @if (Services != null) {<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
</manifest> @if (Characteristics != null) {<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
</manifest><?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
</manifest>@if (ReadResult != null) {<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
</manifest><?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
</manifest> } } } }@if (BleInfo.Name != null){ 历史连接
@BleInfo.Name
@BleInfo.DeviceID
@BleInfo.Serviceid
@BleInfo.Characteristic
@ReadResult
}@Message10. 运行
11. 相关资料
如何远程调试 MAUI blazor / Blazor Hybrid
https://www.cnblogs.com/densen2014/p/16988516.html
来源:https://www.cnblogs.com/densen2014/archive/2023/08/23/17649961.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
页:
[1]