Skip to content

Commit

Permalink
feat(IBluetoothService): add BluetoothFilterOption parameter (#4496)
Browse files Browse the repository at this point in the history
* feat: 增加 BluetoothRequestOptions 参数

* refactor: 更新脚本

* doc: 更新示例

* doc: 更新示例

* test: 更新单元测试

* chore: bump version 8.10.4

* test: 更新单元测试
  • Loading branch information
ArgoZhang authored Oct 21, 2024
1 parent 697fdea commit 259b6f2
Show file tree
Hide file tree
Showing 11 changed files with 222 additions and 14 deletions.
4 changes: 2 additions & 2 deletions src/BootstrapBlazor.Server/Components/Samples/Bluetooth.razor
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ private IBluetoothService? BluetoothService { get; set; }</Pre>
private IBluetoothService? BluetoothService { get; set; }</Pre>

<p class="code-label">2. 列出蓝牙设备</p>
<p>调用 <code>BluetoothService</code> 实例方法 <code>RequestDevice</code> 即可,通过 <code>IsSupport</code> 进行浏览器是否支持蓝牙</p>
<p>调用 <code>BluetoothService</code> 实例方法 <code>RequestDevice</code> 即可,通过 <code>IsSupport</code> 进行浏览器是否支持蓝牙。可以通过 <code>BluetoothRequestOptions</code> 过滤参数过滤蓝牙设备</p>

<Pre>_serialPort = await BluetoothService.RequestDevice(["battery_service"]);
<Pre>_serialPort = await BluetoothService.RequestDevice();
if (BluetoothService.IsSupport == false)
{
await ToastService.Error(Localizer["NotSupportBluetoothTitle"], Localizer["NotSupportBluetoothContent"]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public partial class Bluetooth

private async Task RequestDevice()
{
_blueDevice = await BluetoothService.RequestDevice(["battery_service"]);
_blueDevice = await BluetoothService.RequestDevice();
if (BluetoothService.IsSupport == false)
{
await ToastService.Error(Localizer["NotSupportBluetoothTitle"], Localizer["NotSupportBluetoothContent"]);
Expand Down
2 changes: 1 addition & 1 deletion src/BootstrapBlazor/BootstrapBlazor.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<Version>8.10.4-beta03</Version>
<Version>8.10.4</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
43 changes: 43 additions & 0 deletions src/BootstrapBlazor/Services/Bluetooth/BluetoothFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

using System.Text.Json.Serialization;

namespace BootstrapBlazor.Components;

/// <summary>
/// BluetoothFilter 类
/// </summary>
public class BluetoothFilter
{
/// <summary>
/// An array of values indicating the Bluetooth GATT (Generic Attribute Profile) services that a Bluetooth device must support. Each value can be a valid name from the GATT assigned services list, such as 'battery_service' or 'blood_pressure'. You can also pass a full service UUID such as '0000180F-0000-1000-8000-00805f9b34fb' or the short 16-bit (0x180F) or 32-bit alias. Note that these are the same values that can be passed to BluetoothUUID.getService().
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<string>? Services { get; set; }

/// <summary>
/// A string containing the precise name of the device to match against.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Name { get; set; }

/// <summary>
/// A string containing the name prefix to match against. All devices that have a name starting with this string will be matched.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? NamePrefix { get; set; }

/// <summary>
/// An array of objects matching against manufacturer data in the Bluetooth Low Energy (BLE) advertising packets.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<BluetoothManufacturerDataFilter>? ManufacturerData { get; set; }

/// <summary>
/// An array of objects matching against service data in the Bluetooth Low Energy (BLE) advertising packets.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<BluetoothServiceDataFilter>? ServiceData { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

using System.Text.Json.Serialization;

namespace BootstrapBlazor.Components;

/// <summary>
/// BluetoothManufacturerDataFilter 配置类
/// </summary>
public class BluetoothManufacturerDataFilter
{
/// <summary>
/// A mandatory number identifying the manufacturer of the device. Company identifiers are listed in the Bluetooth specification Assigned numbers, Section 7. For example, to match against devices manufactured by "Digianswer A/S", with assigned hex number 0x000C, you would specify 12.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public int? CompanyIdentifier { get; set; }

/// <summary>
/// The data prefix. A buffer containing values to match against the values at the start of the advertising manufacturer data.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? DataPrefix { get; set; }

/// <summary>
/// This allows you to match against bytes within the manufacturer data, by masking some bytes of the service data dataPrefix.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Mask { get; set; }
}
43 changes: 43 additions & 0 deletions src/BootstrapBlazor/Services/Bluetooth/BluetoothRequestOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

using System.Text.Json.Serialization;

namespace BootstrapBlazor.Components;

/// <summary>
/// BluetoothRequestOptions 参数类
/// </summary>
public class BluetoothRequestOptions
{
/// <summary>
/// An array of filter objects indicating the properties of devices that will be matched. To match a filter object, a device must match all the values of the filter: all its specified services, name, namePrefix, and so on
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<BluetoothFilter>? Filters { get; set; }

/// <summary>
/// An array of filter objects indicating the characteristics of devices that will be excluded from matching. The properties of the array elements are the same as for <see cref="Filters"/>.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<BluetoothFilter>? ExclusionFilters { get; set; }

/// <summary>
/// An array of optional service identifiers.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<string>? OptionalServices { get; set; }

/// <summary>
/// An optional array of integer manufacturer codes. This takes the same values as companyIdentifier.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<string>? OptionalManufacturerData { get; set; }

/// <summary>
/// A boolean value indicating that the requesting script can accept all Bluetooth devices. The default is false.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public bool AcceptAllDevices { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

using System.Text.Json.Serialization;

namespace BootstrapBlazor.Components;

/// <summary>
/// BluetoothServiceDataFilter 配置类
/// </summary>
public class BluetoothServiceDataFilter
{
/// <summary>
/// The GATT service name, the service UUID, or the UUID 16-bit or 32-bit form. This takes the same values as the elements of the services array.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Service { get; set; }

/// <summary>
/// The data prefix. A buffer containing values to match against the values at the start of the advertising service data.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? DataPrefix { get; set; }

/// <summary>
/// This allows you to match against bytes within the manufacturer data, by masking some bytes of the service data dataPrefix.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Mask { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@ public async Task<bool> GetAvailability(CancellationToken token = default)
/// <summary>
/// <inheritdoc />
/// </summary>
public async Task<IBluetoothDevice?> RequestDevice(string[] optionalServices, CancellationToken token = default)
public async Task<IBluetoothDevice?> RequestDevice(BluetoothRequestOptions? options = null, CancellationToken token = default)
{
_module ??= await LoadModule();

BluetoothDevice? device = null;
if (IsSupport)
{
ErrorMessage = null;
var parameters = await _module.InvokeAsync<string[]?>("requestDevice", token, _deviceId, optionalServices, _interop, nameof(OnError));
var parameters = await _module.InvokeAsync<string[]?>("requestDevice", token, _deviceId, options, _interop, nameof(OnError));
if (parameters != null)
{
device = new BluetoothDevice(_module, _deviceId, parameters);
Expand Down
4 changes: 2 additions & 2 deletions src/BootstrapBlazor/Services/Bluetooth/IBluetoothService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ public interface IBluetoothService
/// <summary>
/// 请求蓝牙配对方法
/// </summary>
/// <param name="optionalServices">请求服务列表 请参考 https://github.com/WebBluetoothCG/registries/blob/master/gatt_assigned_services.txt</param>
/// <param name="options"><see cref="BluetoothRequestOptions"/> 实例</param>
/// <param name="token"></param>
/// <returns></returns>
Task<IBluetoothDevice?> RequestDevice(string[] optionalServices, CancellationToken token = default);
Task<IBluetoothDevice?> RequestDevice(BluetoothRequestOptions? options = null, CancellationToken token = default);
}
7 changes: 3 additions & 4 deletions src/BootstrapBlazor/wwwroot/modules/bt.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export async function getAvailability() {
return ret;
}

export async function requestDevice(id, optionalServices, invoke, method) {
export async function requestDevice(id, options, invoke, method) {
let ret = await getAvailability();
if (ret === false) {
return null;
Expand All @@ -22,9 +22,8 @@ export async function requestDevice(id, optionalServices, invoke, method) {
const bt = { device: null };
Data.set(id, bt);
try {
const ret = await navigator.bluetooth.requestDevice({
acceptAllDevices: true,
optionalServices: optionalServices
const ret = await navigator.bluetooth.requestDevice(options ?? {
acceptAllDevices: true
});
bt.device = ret;
device = [ret.name, ret.id];
Expand Down
65 changes: 63 additions & 2 deletions test/UnitTest/Services/BluetoothServiceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public async Task RequestDevice_Ok()
Context.JSInterop.Setup<bool>("disconnect", matcher => matcher.Arguments.Count == 3 && (matcher.Arguments[0]?.ToString()?.StartsWith("bb_bt_") ?? false)).SetResult(true);

var bluetoothService = Context.Services.GetRequiredService<IBluetoothService>();
var device = await bluetoothService.RequestDevice(["battery_service"]);
var device = await bluetoothService.RequestDevice();
Assert.NotNull(device);
Assert.Equal("test", device.Name);
Assert.Equal("id_1234", device.Id);
Expand Down Expand Up @@ -50,7 +50,7 @@ public async Task ReadValue_null()
Context.JSInterop.Setup<string[]?>("requestDevice", matcher => matcher.Arguments.Count == 4 && (matcher.Arguments[0]?.ToString()?.StartsWith("bb_bt_") ?? false)).SetResult(["test", "id_1234"]);
Context.JSInterop.Setup<byte[]?>("readValue", matcher => matcher.Arguments.Count == 5 && (matcher.Arguments[0]?.ToString()?.StartsWith("bb_bt_") ?? false)).SetResult(null);
var bluetoothService = Context.Services.GetRequiredService<IBluetoothService>();
var device = await bluetoothService.RequestDevice(["battery_service"]);
var device = await bluetoothService.RequestDevice();
Assert.NotNull(device);
var v = await device.GetBatteryValue();
Assert.Equal(0x0, v);
Expand All @@ -73,4 +73,65 @@ public async Task GetAvailability_Ok()
mi.Invoke(bluetoothService, ["test"]);
Assert.Equal("test", bluetoothService.ErrorMessage);
}

[Fact]
public void Filter_Ok()
{
var filter = new BluetoothRequestOptions()
{
Filters =
[
new() {
ManufacturerData =
[
new()
{
CompanyIdentifier = 0x004C,
DataPrefix = "Apple",
Mask = "test"
}
],
Name = "test-Name",
NamePrefix = "test-NamePrefix",
Services = ["test-service"],
ServiceData = [new BluetoothServiceDataFilter()
{
DataPrefix = "test-data-prefix",
Service = "test-data-service",
Mask = "test-data-mask"
}]
}
],
AcceptAllDevices = false,
ExclusionFilters = [],
OptionalManufacturerData = ["test-manufacturer-data"],
OptionalServices = ["test-optional-service"]
};

Assert.NotNull(filter.Filters);

var data = filter.Filters[0].ManufacturerData;
Assert.NotNull(data);
Assert.Equal(0x004C, data[0].CompanyIdentifier);
Assert.Equal("Apple", data[0].DataPrefix);
Assert.Equal("test", data[0].Mask);

Assert.Equal("test-Name", filter.Filters[0].Name);
Assert.Equal("test-NamePrefix", filter.Filters[0].NamePrefix);

var services = filter.Filters[0].Services;
Assert.NotNull(services);
Assert.Equal("test-service", services[0]);

var serviceData = filter.Filters[0].ServiceData;
Assert.NotNull(serviceData);
Assert.Equal("test-data-prefix", serviceData[0].DataPrefix);
Assert.Equal("test-data-service", serviceData[0].Service);
Assert.Equal("test-data-mask", serviceData[0].Mask);

Assert.False(filter.AcceptAllDevices);
Assert.Empty(filter.ExclusionFilters);
Assert.Equal(["test-manufacturer-data"], filter.OptionalManufacturerData);
Assert.Equal(["test-optional-service"], filter.OptionalServices);
}
}

0 comments on commit 259b6f2

Please sign in to comment.