Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 ([email protected]). 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 ([email protected]). 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 ([email protected]). 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 ([email protected]). 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);
}
}