Skip to content

Commit e94371a

Browse files
authored
feat(IBluttooth): add Start/StopNotifications method (#4510)
* chore: 重新设计蓝牙接口定义 * refactor: 重新设计蓝牙接口 * doc: 更新文档 * refactor: 移除 Connect 属性 * doc: 更新比对逻辑 * feat: 完善 StartNotifications 方法 * feat: 增加 remove 扩展方法 * feat: 增加 StopNotifications 方法 * feat: 增加资源销毁逻辑 * refactor: 更新参数名称 * feat: 增加重复判断 * refactor: 移除日志 * refactor: 更改 ReadValue 方法返回值 * test: 更新单元测试 * test: 更新单元测试 * feat: 增加 IsNotify 属性 * test: 更新单元测试 * doc: 更新文档
1 parent 3af183c commit e94371a

File tree

17 files changed

+809
-217
lines changed

17 files changed

+809
-217
lines changed

src/BootstrapBlazor.Server/Components/Samples/Bluetooth.razor

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<p>@((MarkupString)Localizer["BluetoothDescription"].Value)</p>
99

1010
<Pre>[Inject, NotNull]
11-
private IBluetoothService? BluetoothService { get; set; }</Pre>
11+
private IBluetooth? BluetoothService { get; set; }</Pre>
1212

1313
<Tips>
1414
<ul class="ul-demo">
@@ -59,31 +59,37 @@ private IBluetoothService? BluetoothService { get; set; }</Pre>
5959
</div>
6060
</div>
6161
<div class="col-12">
62-
<Button Text="@Localizer["BluetoothGetServicesText"]" Icon="fa-solid fa-microchip" IsDisabled="@(_blueDevice is not { Connected: true })" OnClick="GetServices"></Button>
62+
<Button Text="@Localizer["BluetoothGetServicesText"]" Icon="fa-solid fa-server" IsDisabled="@(_blueDevice is not { Connected: true })" OnClick="GetServices"></Button>
6363
</div>
6464
<div class="col-12">
6565
<Select Items="ServicesList" @bind-Value="_selectedService" class="flex-fill"></Select>
6666
</div>
6767
<div class="col-12">
68-
<Button Text="@Localizer["BluetoothGetCharacteristicsText"]" Icon="fa-solid fa-microchip" IsDisabled="@(_blueDevice is not { Connected: true })" OnClick="GetCharacteristics"></Button>
68+
<Button Text="@Localizer["BluetoothGetCharacteristicsText"]" Icon="fa-solid fa-plug" IsDisabled="@(_blueDevice is not { Connected: true })" OnClick="GetCharacteristics"></Button>
6969
</div>
7070
<div class="col-12">
7171
<Select Items="CharacteristicsList" @bind-Value="_selectedCharacteristic" class="flex-fill"></Select>
7272
</div>
7373
<div class="col-12">
74-
<Button Text="@Localizer["BluetoothReadValueText"]" Icon="fa-solid fa-microchip" IsDisabled="@(_blueDevice is not { Connected: true })" OnClick="ReadValue"></Button>
74+
<Button Text="@Localizer["BluetoothReadValueText"]" Icon="fa-solid fa-memory" IsDisabled="@(_blueDevice is not { Connected: true })" OnClick="ReadValue"></Button>
7575
</div>
7676
<div class="col-12">
7777
<Textarea Value="@_readValueString" rows="3" readonly></Textarea>
7878
</div>
79+
<div class="col-12">
80+
<Button Text="@Localizer["BluetoothStartText"]" Icon="fa-solid fa-play" IsDisabled="@(_blueDevice is not { Connected: true })" OnClick="StartNotifications"></Button>
81+
</div>
82+
<div class="col-12">
83+
<Button Text="@Localizer["BluetoothStopText"]" Icon="fa-solid fa-stop" IsDisabled="@(_blueDevice is not { Connected: true })" OnClick="StopNotifications"></Button>
84+
</div>
7985
</div>
8086
</DemoBlock>
8187

8288
<p class="code-label mt-3">1. 服务注入</p>
8389

8490
<Pre>[Inject]
8591
[NotNull]
86-
private IBluetoothService? BluetoothService { get; set; }</Pre>
92+
private IBluetooth? BluetoothService { get; set; }</Pre>
8793

8894
<p class="code-label">2. 列出蓝牙设备</p>
8995
<p>调用 <code>BluetoothService</code> 实例方法 <code>RequestDevice</code> 即可,通过 <code>IsSupport</code> 进行浏览器是否支持蓝牙。可以通过 <code>BluetoothRequestOptions</code> 过滤参数过滤蓝牙设备</p>
@@ -130,18 +136,28 @@ if (BluetoothService.IsSupport == false)
130136

131137
<p><code>IBluetoothService</code> 实例属性 <code>IsSupport</code> 是表示当前浏览器是否支持蓝牙功能</p>
132138

133-
<p><code>IBluetoothService</code> 与 <code>IBluetoothDevice</code> 所有实例方法均有返回值,可通过查看其实例属性 <code>ErrorMessage</code> 获得上一次执行的错误描述信息</p>
139+
<p><code>IBluetooth</code> 与 <code>IBluetoothDevice</code> 所有实例方法均有返回值,可通过查看其实例属性 <code>ErrorMessage</code> 获得上一次执行的错误描述信息</p>
134140

135-
<p><code>IBluetoothDevice</code> 实例方法 <code>ReadValue</code> 是通用方法,通过参数指定<code>Services</code> 与 <code>Characteristics</code></p>
141+
<p><code>IBluetoothDevice</code> 实例方法 <code>ReadValue</code> 是通用方法,通过参数指定<code>ServiceUUID</code> 与 <code>CharacteristicUUID</code></p>
136142

137143
<p>原生方法 <code>getDevices</code> 暂未封装,因为需要设置浏览器才能开启</p>
138144

139-
<p>可根据自己的业务需求自定义扩展方法,内置扩展方法列表如下:</p>
145+
<p>可根据自己的业务需求自定义扩展方法,内置方法列表如下:</p>
140146

141147
<ul class="ul-demo">
148+
<li><code>GetDeviceInfo</code> 读取设备信息</li>
149+
<li><code>GetCurrentTime</code> 读取当前时间</li>
142150
<li><code>GetBatteryValue</code> 读取电量方法</li>
143151
</ul>
144152

153+
<p class="code-label">接口说明</p>
154+
<ul class="ul-demo">
155+
<li><code>IBluetooth</code> 蓝牙服务(系统注入)</li>
156+
<li><code>IBluetoothDevice</code> 蓝牙设备</li>
157+
<li><code>IBluetoothService</code> 蓝牙设备提供的服务接口 如时间服务</li>
158+
<li><code>IBluetoothCharacteristic</code> 蓝牙设备服务提供的特性接口 如时间服务的当前时间特性(还有时区特性)</li>
159+
</ul>
160+
145161
<p class="code-label">相关文档</p>
146162

147163
<ul class="ul-demo">

src/BootstrapBlazor.Server/Components/Samples/Bluetooth.razor.cs

Lines changed: 72 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ namespace BootstrapBlazor.Server.Components.Samples;
1010
/// <summary>
1111
/// Bluetooth
1212
/// </summary>
13-
public partial class Bluetooth
13+
public partial class Bluetooth : IAsyncDisposable
1414
{
1515
[Inject, NotNull]
16-
private IBluetoothService? BluetoothService { get; set; }
16+
private IBluetooth? BluetoothService { get; set; }
1717

1818
[Inject, NotNull]
1919
private ToastService? ToastService { get; set; }
@@ -30,18 +30,18 @@ public partial class Bluetooth
3030

3131
private string? _readValueString = null;
3232

33-
private List<string> _services = [];
33+
private List<IBluetoothService> _bluetoothServices = [];
3434

35-
private List<string> _characteristics = [];
35+
private List<IBluetoothCharacteristic> _bluetoothCharacteristics = [];
3636

3737
private string? _selectedService;
3838

3939
private string? _selectedCharacteristic;
4040

41-
private List<SelectedItem> ServicesList => _services.Select(i => new SelectedItem(i, FormatServiceName(i))).ToList();
42-
43-
private List<SelectedItem> CharacteristicsList => _characteristics.Select(i => new SelectedItem(i, FormatCharacteristicsName(i))).ToList();
41+
private List<SelectedItem> ServicesList => _bluetoothServices.Select(i => new SelectedItem(i.UUID.ToUpperInvariant(), FormatServiceName(i))).ToList();
4442

43+
private List<SelectedItem> CharacteristicsList => _bluetoothCharacteristics.Select(i => new SelectedItem(i.UUID.ToUpperInvariant(), FormatCharacteristicsName(i))).ToList();
44+
4545
private Dictionary<string, string> ServiceUUIDList = [];
4646

4747
/// <summary>
@@ -58,13 +58,14 @@ protected override void OnInitialized()
5858
}).ToDictionary();
5959
}
6060

61-
private string FormatServiceName(string serviceName)
61+
private string FormatServiceName(IBluetoothService service)
6262
{
63-
var name = ServiceUUIDList[serviceName.ToUpperInvariant()];
64-
return $"{name}({serviceName.ToUpperInvariant()})";
63+
var uuId = service.UUID.ToUpperInvariant();
64+
return ServiceUUIDList.TryGetValue(uuId, out var serviceName)
65+
? $"{serviceName}({uuId})" : uuId;
6566
}
6667

67-
private string FormatCharacteristicsName(string characteristicName) => characteristicName.ToUpperInvariant();
68+
private string FormatCharacteristicsName(IBluetoothCharacteristic characteristic) => characteristic.UUID.ToUpperInvariant();
6869

6970
private async Task RequestDevice()
7071
{
@@ -73,6 +74,10 @@ private async Task RequestDevice()
7374
AcceptAllDevices = true,
7475
OptionalServices = ["device_information", "current_time", "battery_service"]
7576
};
77+
#if DEBUG
78+
options.AcceptAllDevices = false;
79+
options.Filters = [ new BluetoothFilter() { NamePrefix = "Argo" } ];
80+
#endif
7681
_blueDevice = await BluetoothService.RequestDevice(options);
7782
if (BluetoothService.IsSupport == false)
7883
{
@@ -112,8 +117,8 @@ private async Task Disconnect()
112117
_batteryValue = null;
113118
_batteryValueString = null;
114119
_deviceInfoList.Clear();
115-
_services.Clear();
116-
_characteristics.Clear();
120+
_bluetoothServices.Clear();
121+
_bluetoothCharacteristics.Clear();
117122
_readValueString = null;
118123
}
119124
}
@@ -174,28 +179,78 @@ private async Task GetServices()
174179
{
175180
if (_blueDevice != null)
176181
{
177-
_services = await _blueDevice.GetPrimaryServices();
182+
_bluetoothServices = await _blueDevice.GetPrimaryServices();
178183
}
179184
}
180185

181186
private async Task GetCharacteristics()
182187
{
183188
if (_blueDevice != null && !string.IsNullOrEmpty(_selectedService))
184189
{
185-
_characteristics = await _blueDevice.GetCharacteristics(_selectedService);
190+
_bluetoothCharacteristics.Clear();
191+
var service = _bluetoothServices.Find(i => i.UUID.ToUpperInvariant() == _selectedService);
192+
if (service != null)
193+
{
194+
_bluetoothCharacteristics = await service.GetCharacteristics();
195+
}
186196
}
187197
}
188198

189199
private async Task ReadValue()
190200
{
191201
_readValueString = null;
192-
if (_blueDevice != null && !string.IsNullOrEmpty(_selectedService) && !string.IsNullOrEmpty(_selectedCharacteristic))
202+
var characteristics = _bluetoothCharacteristics.Find(i => i.UUID.ToUpperInvariant() == _selectedCharacteristic);
203+
if (characteristics != null)
193204
{
194-
var data = await _blueDevice.ReadValue(_selectedService, _selectedCharacteristic);
205+
var data = await characteristics.ReadValue();
195206
if (data != null)
196207
{
197208
_readValueString = string.Join(" ", data.Select(i => Convert.ToString(i, 16).PadLeft(2, '0').ToUpperInvariant()));
198209
}
199210
}
200211
}
212+
213+
private async Task StartNotifications()
214+
{
215+
var characteristics = _bluetoothCharacteristics.Find(i => i.UUID.ToUpperInvariant() == _selectedCharacteristic);
216+
if (characteristics != null)
217+
{
218+
await characteristics.StartNotifications(HandlerNotification);
219+
}
220+
}
221+
222+
private async Task StopNotifications()
223+
{
224+
var characteristics = _bluetoothCharacteristics.Find(i => i.UUID.ToUpperInvariant() == _selectedCharacteristic);
225+
if (characteristics != null)
226+
{
227+
await characteristics.StopNotifications();
228+
}
229+
}
230+
231+
private Task HandlerNotification(byte[] payload) {
232+
_readValueString = string.Join(" ", payload.Select(i => Convert.ToString(i, 16).PadLeft(2, '0').ToUpperInvariant()));
233+
StateHasChanged();
234+
return Task.CompletedTask;
235+
}
236+
237+
private async ValueTask DisposeAsync(bool disposing)
238+
{
239+
if (disposing)
240+
{
241+
if (_blueDevice != null)
242+
{
243+
await _blueDevice.DisposeAsync();
244+
}
245+
}
246+
}
247+
248+
/// <summary>
249+
/// <inheritdoc/>
250+
/// </summary>
251+
/// <returns></returns>
252+
public async ValueTask DisposeAsync() {
253+
await DisposeAsync(true);
254+
GC.SuppressFinalize(this);
255+
}
201256
}

src/BootstrapBlazor.Server/Locales/en-US.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5892,7 +5892,7 @@
58925892
"BluetoothDescription": "Allows websites to communicate with Bluetooth devices connected to the user's computer. For example, you can connect to a Bluetooth printer to print.",
58935893
"BluetoothTipsLi1": "This feature is available only in secure contexts (HTTPS)",
58945894
"BluetoothTipsLi2": "This is an experimental technology Check the <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Bluetooth#browser_compatibility\" target=\"_blank\">Browser compatibility table</a> carefully before using this in production",
5895-
"BluetoothTipsTitle": "Note: The <code>IBluetoothService</code> interface instance inherits <code>IAsyncDisposable</code>. When switching routes, you need to release its resources by calling its <code>DisposeAsync</code>.",
5895+
"BluetoothTipsTitle": "Note: The <code>IBluetoothDevice</code> interface instance inherits <code>IAsyncDisposable</code>. When switching routes, you need to release its resources by calling its <code>DisposeAsync</code>.",
58965896
"NotSupportBluetoothTitle": "Scan Devices",
58975897
"NotSupportBluetoothContent": "The current browser does not support serial port operations. Please change to Edge or Chrome browser.",
58985898
"BluetoothRequestText": "Scan",
@@ -5901,7 +5901,7 @@
59015901
"BluetoothGetBatteryText": "Battery",
59025902
"BluetoothGetHeartRateText": "HeartRate",
59035903
"BaseUsageTitle": "Basic usage",
5904-
"BaseUsageIntro": "Request communication with Bluetooth devices through the <code>IBluetoothService</code> service",
5904+
"BaseUsageIntro": "Request communication with Bluetooth devices through the <code>IBluetooth</code> service",
59055905
"BluetoothGetCurrentTimeText": "Time",
59065906
"BluetoothDeviceInfoText": "Device Info",
59075907
"UsageDesc": "Click the Scan button and select the phone to test in the pop-up window",

src/BootstrapBlazor.Server/Locales/zh-CN.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5887,12 +5887,12 @@
58875887
"OnScreenKeyboardsBasicTitle": "基础用法"
58885888
},
58895889
"BootstrapBlazor.Server.Components.Samples.Bluetooth": {
5890-
"BluetoothTitle": "IBluetoothService 蓝牙服务",
5890+
"BluetoothTitle": "IBluetooth 蓝牙服务",
58915891
"BluetoothIntro": "提供了查询蓝牙可用性和请求访问设备的方法",
58925892
"BluetoothDescription": "允许网站与连接到用户计算机的蓝牙设备进行通信。例如可以连接蓝牙打印机进行打印操作",
58935893
"BluetoothTipsLi1": "该功能仅在部分或所有支持浏览器的安全上下文(HTTPS)中可用",
58945894
"BluetoothTipsLi2": "这是一项实验性技术,在生产中使用之前请仔细,检查 <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Bluetooth#browser_compatibility\" target=\"_blank\">浏览器兼容性表</a>",
5895-
"BluetoothTipsTitle": "注意:<code>IBluetoothService</code> 接口实例继承 <code>IAsyncDisposable</code> 路由切换时需要对其进行资源释放,调用其 <code>DisposeAsync</code> 即可",
5895+
"BluetoothTipsTitle": "注意:<code>IBluetoothDevice</code> 接口实例继承 <code>IAsyncDisposable</code> 路由切换时需要对其进行资源释放,调用其 <code>DisposeAsync</code> 即可",
58965896
"NotSupportBluetoothTitle": "扫描设备",
58975897
"NotSupportBluetoothContent": "当前浏览器不支持串口操作,请更换 Edge 或者 Chrome 浏览器",
58985898
"BluetoothRequestText": "扫描",
@@ -5901,7 +5901,7 @@
59015901
"BluetoothGetBatteryText": "读取电量",
59025902
"BluetoothGetHeartRateText": "读取心率",
59035903
"BaseUsageTitle": "基础用法",
5904-
"BaseUsageIntro": "通过 <code>IBluetoothService</code> 服务,请求与蓝牙设备通讯",
5904+
"BaseUsageIntro": "通过 <code>IBluetooth</code> 服务,请求与蓝牙设备通讯",
59055905
"BluetoothGetCurrentTimeText": "读取时间",
59065906
"BluetoothDeviceInfoText": "读取硬件信息",
59075907
"UsageDesc": "点击扫描按钮,在弹窗中选中手机进行测试",

src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public static IServiceCollection AddBootstrapBlazor(this IServiceCollection serv
7070
services.TryAddScoped<IComponentHtmlRenderer, ComponentHtmlRenderer>();
7171
services.TryAddScoped<IBrowserFingerService, DefaultBrowserFingerService>();
7272
services.TryAddScoped<ISerialService, DefaultSerialService>();
73-
services.TryAddScoped<IBluetoothService, DefaultBluetoothService>();
73+
services.TryAddScoped<IBluetooth, DefaultBluetooth>();
7474
services.AddScoped<TabItemTextOptions>();
7575
services.AddScoped<DialogService>();
7676
services.AddScoped<MaskService>();

src/BootstrapBlazor/Extensions/IBluetoothDeviceExtensions.cs

Lines changed: 0 additions & 28 deletions
This file was deleted.

0 commit comments

Comments
 (0)