bh003- Blazor hybrid / Maui 使用蓝牙BLE快速教程

  • bh003- Blazor hybrid / Maui 使用蓝牙BLE快速教程已关闭评论
  • 176 次浏览
  • A+
所属分类:.NET技术
摘要

源码BlazorHybrid.Maui.Permissions 因为源码比较长,主要是一些检查和申请权限,BLE权限相关代码,就不占用篇幅列出,感兴趣的同学直接打开源码参考


1. 建立工程 bh003_ble

源码

2. 添加 nuget 包

<PackageReference Include="BlazorHybrid.Maui.Permissions" Version="0.0.2" /> <PackageReference Include="BootstrapBlazor" Version="7.*" /> <PackageReference Include="Densen.Extensions.BootstrapBlazor" Version="7.*" /> 

BlazorHybrid.Maui.Permissions 因为源码比较长,主要是一些检查和申请权限,BLE权限相关代码,就不占用篇幅列出,感兴趣的同学直接打开源码参考

顺便打开可空 <Nullable>enable</Nullable>

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" />    <!-- csproj文件指定SupportedOSPlatformVersion android 28.0 可以继续使用安卓9的权限 -->   <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"/>    <!-- csproj文件指定SupportedOSPlatformVersion android 31.0 使用安卓12的权限 -->   <!-- Android 12以下才需要定位权限,Android 9以下官方建议申请ACCESS_COARSE_LOCATION -->    <!--<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="30"/> 	<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30"/> 	<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30"/> 	<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30"/>-->    <!-- Android 12在不申请定位权限时,必须加上android:usesPermissionFlags="neverForLocation",否则搜不到设备 -->   <uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation"/>    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />   <!--蓝牙 END--> </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> 

以下是完整文件

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict>     <key>LSRequiresIPhoneOS</key>     <true/>     <key>UIDeviceFamily</key>     <array>         <integer>1</integer>         <integer>2</integer>     </array>     <key>UIRequiredDeviceCapabilities</key>     <array>         <string>arm64</string>     </array>     <key>UISupportedInterfaceOrientations</key>     <array>         <string>UIInterfaceOrientationPortrait</string>         <string>UIInterfaceOrientationLandscapeLeft</string>         <string>UIInterfaceOrientationLandscapeRight</string>     </array>     <key>UISupportedInterfaceOrientations~ipad</key>     <array>         <string>UIInterfaceOrientationPortrait</string>         <string>UIInterfaceOrientationPortraitUpsideDown</string>         <string>UIInterfaceOrientationLandscapeLeft</string>         <string>UIInterfaceOrientationLandscapeRight</string>     </array>     <key>XSAppIconAssets</key>     <string>Assets.xcassets/appicon.appiconset</string>     <key>UIBackgroundModes</key>     <array>         <string>bluetooth-central</string>         <string>bluetooth-peripheral</string>     </array>     <key>NSBluetoothPeripheralUsageDescription</key>     <string>此应用程序需要访问您的蓝牙。请根据要求授予权限.</string>     <key>NSBluetoothAlwaysUsageDescription</key>     <string>此应用程序需要访问您的蓝牙。请根据要求授予权限.</string>  </dict> </plist> 

Windows

Package.appxmanifest

bh003- Blazor hybrid / Maui 使用蓝牙BLE快速教程

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>      <div class="status-bar-safe-area"></div>      <div id="app">Loading...</div>      <div id="blazor-error-ui">         An unhandled error has occurred.         <a href="" class="reload">Reload</a>         <a class="dismiss"><i class="fa-solid fa-xmark"></i></a>     </div>      <script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"></script>     <script src="_framework/blazor.webview.js" autostart="false"></script>  </body>  </html> 

bh003- Blazor hybrid / Maui 使用蓝牙BLE快速教程

5. 添加 BootstrapBlazorRoot 组件

Main.razor 文件添加 BootstrapBlazorRoot 组件

bh003- Blazor hybrid / Maui 使用蓝牙BLE快速教程

6. 添加命名空间引用

_Imports.razor

@using BootstrapBlazor.Components 

7. 添加服务

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<App>()                 .ConfigureFonts(fonts =>                 {                     fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");                 });              builder.Services.AddMauiBlazorWebView();  #if DEBUG 		builder.Services.AddBlazorWebViewDeveloperTools(); 		builder.Logging.AddDebug(); #endif              builder.Services.AddSingleton<WeatherForecastService>();             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 {     [Inject, NotNull]     BluetoothLEServices? MyBleTester { get; set; }     [Inject, NotNull] protected IStorage? Storage { get; set; }     [Inject, NotNull] 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}rn{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 "/"  <h3>蓝牙</h3>  <div class="row g-3">     <div class="btn-group" role="group">         <Button Text="扫描外设" @attributes=IsScanningCss OnClick=ScanDevice />         @if (Devices != null)         {             <Button Text="连接" OnClick="OnDeviceSelect" />             <Button Text="断开" OnClick="OnDisConnectDevice" />             @if (Characteristics != null)             {                 <Button Text="写入" OnClick="ReadDeviceName" />                 <Button Text="读取" OnClick="ReadDeviceName" />             }         }     </div> </div> @if (Devices != null) {      <div class="row g-3">         <div class="col-12 col-sm-3">             <Select TValue="Guid" Items="DeviceList" OnSelectedItemChanged="OnDeviceSelect" />         </div>         @if (Services != null)         {             <div class="col-12 col-sm-3">                 <Select TValue="Guid" Items="ServiceidList" OnSelectedItemChanged="OnServiceidSelect" />             </div>             @if (Characteristics != null)             {                 <div class="col-12 col-sm-3">                     <Select TValue="Guid" Items="CharacteristicList" OnSelectedItemChanged="OnCharacteristSelect" />                 </div>                 @if (ReadResult != null)                 {                     <div class="col-12 col-sm-3">                         <Display TValue="string" Value="@ReadResult" />                     </div>                 }             }         }     </div>  }  @if (BleInfo.Name != null) {      <div class="g-3">         历史连接 <br />         @BleInfo.Name <br />         @BleInfo.DeviceID <br />         @BleInfo.Serviceid <br />         @BleInfo.Characteristic <br />         @ReadResult <br />     </div>  } <Switch DisplayText="自动连接" OnText="自动连接" OffText="手动连接" Value="@IsAuto" OnValueChanged="@OnStateChanged" />  <pre style="max-height: 500px; overflow-y: scroll; white-space: pre-wrap; word-wrap: break-word;">@Message</pre>  

10. 运行

bh003- Blazor hybrid / Maui 使用蓝牙BLE快速教程

bh003- Blazor hybrid / Maui 使用蓝牙BLE快速教程

bh003- Blazor hybrid / Maui 使用蓝牙BLE快速教程

11. 相关资料

如何远程调试 MAUI blazor / Blazor Hybrid
https://www.cnblogs.com/densen2014/p/16988516.html