Skip to content

Commit 6a29fe1

Browse files
committed
- Enable interface Claims
- Several fixes
1 parent fe8b711 commit 6a29fe1

File tree

14 files changed

+261
-117
lines changed

14 files changed

+261
-117
lines changed

src/Blazor.Extensions.WebUSB.JS/src/USBManager.ts

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
1-
import { USBDeviceFound, USBRequestDeviceOptions, ParseUSBDevice } from "./USBTypes";
1+
import { USBDeviceFound, USBRequestDeviceOptions, ParseUSBDevice, USBConfiguration, USBInterface } from "./USBTypes";
22

33
type DotNetReferenceType = {
44
invokeMethod<T>(methodIdentifier: string, ...args: any[]): T,
55
invokeMethodAsync<T>(methodIdentifier: string, ...args: any[]): Promise<T>
66
}
77

88
export class USBManager {
9-
private usb: any = (<any>navigator).usb;
10-
private _onConnectCallbackMap: Map<string, (event) => Promise<{}>> = new Map<string, (event) => Promise<{}>>();
9+
private usb: any = (<any>navigator).usb; // The WebUSB API root object
10+
private _foundDevices: any[] = []; // All devices found on the last request
1111

1212
public GetDevices = async (): Promise<USBDeviceFound[]> => {
1313
let devices = await this.usb.getDevices();
1414
let found: USBDeviceFound[] = [];
15-
devices.forEach(d => {
16-
found.push(ParseUSBDevice(d));
17-
});
15+
if (devices) {
16+
devices.forEach(d => {
17+
found.push(ParseUSBDevice(d));
18+
this._foundDevices.push(d);
19+
});
20+
}
1821
return found;
1922
}
2023

@@ -54,25 +57,72 @@ export class USBManager {
5457
return new Promise((resolve, reject) => {
5558
this.usb.requestDevice(reqOptions)
5659
.then(d => {
57-
console.log(d);
58-
resolve(ParseUSBDevice(d));
60+
let usbDevice = ParseUSBDevice(d);
61+
this._foundDevices.push(d);
62+
resolve(usbDevice);
63+
})
64+
.catch(err => reject(err));
65+
});
66+
}
67+
68+
public OpenDevice = (device: USBDeviceFound): Promise<USBDeviceFound> => {
69+
let usbDevice = this.GetUSBDevice(device);
70+
return new Promise<USBDeviceFound>((resolve, reject) => {
71+
if (!usbDevice) return reject("Device not connected");
72+
usbDevice.open()
73+
.then(() => {
74+
resolve(ParseUSBDevice(usbDevice));
5975
})
6076
.catch(err => reject(err));
6177
});
6278
}
6379

64-
public AddOnConnectHandler = (callback: DotNetReferenceType) => {
65-
// TODO: Fix me
66-
const id = callback.invokeMethod<string>("Id");
67-
let localCallback = (event) => callback.invokeMethodAsync(event.device);
68-
this._onConnectCallbackMap.set(id, localCallback);
69-
this.usb.addEventListener("connect", localCallback);
80+
public SelectConfiguration = (device: USBDeviceFound, config: USBConfiguration): Promise<USBDeviceFound> => {
81+
let usbDevice = this.GetUSBDevice(device);
82+
return new Promise<USBDeviceFound>((resolve, reject) => {
83+
if (!usbDevice) return reject("Device not connected");
84+
usbDevice.selectConfiguration(config.configurationValue)
85+
.then(() => {
86+
resolve(ParseUSBDevice(usbDevice));
87+
})
88+
.catch(err => reject(err));
89+
});
90+
}
91+
92+
public ClaimInterface = (device: USBDeviceFound, usbInterface: USBInterface): Promise<USBDeviceFound> => {
93+
let usbDevice = this.GetUSBDevice(device);
94+
return new Promise<USBDeviceFound>((resolve, reject) => {
95+
if (!usbDevice) return reject("Device not connected");
96+
97+
usbDevice.claimInterface(usbInterface.interfaceNumber)
98+
.then(() => {
99+
resolve(ParseUSBDevice(usbDevice));
100+
})
101+
.catch(err => reject(err));
102+
});
103+
}
104+
105+
private GetUSBDevice = (device: USBDeviceFound): any => {
106+
return this._foundDevices.find(
107+
d => d.vendorId == device.vendorId &&
108+
d.productId == device.productId &&
109+
d.deviceClass == device.deviceClass &&
110+
d.serialNumber == device.serialNumber);
111+
}
112+
113+
private FireUSBDeviceEvent = (event: string, eventPayload: any, usb: DotNetReferenceType) => {
114+
let device = ParseUSBDevice(eventPayload.device);
115+
return usb.invokeMethodAsync(event, device);
116+
}
117+
118+
public RegisterUSBEvents = (usb: DotNetReferenceType) => {
119+
//TODO: Check why this event is not consistently being fired.
120+
this.usb.addEventListener("connect", (event) => this.FireUSBDeviceEvent("OnConnect", event, usb));
121+
this.usb.addEventListener("disconnect", (event) => this.FireUSBDeviceEvent("OnDisconnect", event, usb));
70122
}
71123

72-
public RemoveOnConnectHandler = (callback: DotNetReferenceType) => {
73-
const id = callback.invokeMethod<string>("Id");
74-
console.log("Removed " + id);
75-
// TODO: Fix me
76-
//Remove from callback collection
124+
public RemoveUSBEvents = (usb: DotNetReferenceType) => {
125+
this.usb.removeEventListener("connect", (event) => this.FireUSBDeviceEvent("OnConnect", event, usb));
126+
this.usb.removeEventListener("disconnect", (event) => this.FireUSBDeviceEvent("OnDisconnect", event, usb));
77127
}
78128
}

src/Blazor.Extensions.WebUSB.JS/src/USBTypes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export interface USBDeviceFound {
1313
manufacturerName: string,
1414
productName: string,
1515
serialNumber: string,
16-
configuration: USBConfiguration,
16+
configuration: USBConfiguration | null,
1717
configurations: USBConfiguration[],
1818
opened: boolean
1919
}
@@ -42,7 +42,7 @@ export function ParseUSBDevice(rawDevice: any): USBDeviceFound {
4242
manufacturerName: rawDevice.manufacturerName,
4343
productName: rawDevice.productName,
4444
serialNumber: rawDevice.serialNumber,
45-
configuration: ParseUSBConfiguration(rawDevice.configuration),
45+
configuration: rawDevice.configuration != null ? ParseUSBConfiguration(rawDevice.configuration) : null,
4646
configurations: rawDevice.configurations.map(raw => ParseUSBConfiguration(raw)),
4747
opened: rawDevice.opened
4848
};

src/Blazor.Extensions.WebUSB/IUSB.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ namespace Blazor.Extensions.WebUSB
55
{
66
public interface IUSB
77
{
8+
event Action<USBDevice> OnDisconnect;
9+
event Action<USBDevice> OnConnect;
10+
Task Initialize();
811
Task<USBDevice[]> GetDevices();
912
Task<USBDevice> RequestDevice(USBDeviceRequestOptions options = null);
10-
Task<IDisposable> OnConnect(Func<USBDevice, Task> callback);
1113
}
1214
}
Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Linq;
43
using System.Threading.Tasks;
54
using Microsoft.JSInterop;
65

@@ -9,34 +8,63 @@ namespace Blazor.Extensions.WebUSB
98
internal class USB : IUSB
109
{
1110
private static readonly List<USBDeviceFilter> _emptyFilters = new List<USBDeviceFilter>();
11+
private const string REGISTER_USB_METHOD = "BlazorExtensions.WebUSB.RegisterUSBEvents";
12+
private const string REMOVE_USB_METHOD = "BlazorExtensions.WebUSB.RemoveUSBEvents";
1213
private const string REQUEST_DEVICE_METHOD = "BlazorExtensions.WebUSB.RequestDevice";
13-
private const string GET_DEVICE_METHOD = "BlazorExtensions.WebUSB.GetDevices";
14-
private const string ADD_ON_CONNECT_METHOD = "BlazorExtensions.WebUSB.AddOnConnectHandler";
15-
// public event EventHandler OnDisconnect;
14+
private const string GET_DEVICES_METHOD = "BlazorExtensions.WebUSB.GetDevices";
1615

17-
public Task<USBDevice[]> GetDevices() => JSRuntime.Current.InvokeAsync<USBDevice[]>(GET_DEVICE_METHOD);
16+
public event Action<USBDevice> OnDisconnect;
17+
public event Action<USBDevice> OnConnect;
18+
19+
public async Task<USBDevice[]> GetDevices()
20+
{
21+
var devices = await JSRuntime.Current.InvokeAsync<USBDevice[]>(GET_DEVICES_METHOD);
22+
foreach (var device in devices)
23+
{
24+
device.AttachUSB(this);
25+
}
26+
return devices;
27+
}
1828

1929
public async Task<USBDevice> RequestDevice(USBDeviceRequestOptions options = null)
2030
{
2131
try
2232
{
2333
if (options == null)
2434
options = new USBDeviceRequestOptions { Filters = _emptyFilters };
25-
return await JSRuntime.Current.InvokeAsync<USBDevice>(REQUEST_DEVICE_METHOD, options);
35+
var device = await JSRuntime.Current.InvokeAsync<USBDevice>(REQUEST_DEVICE_METHOD, options);
36+
device.AttachUSB(this);
37+
return device;
2638
}
2739
catch (JSException)
2840
{
2941
return null;
3042
}
3143
}
3244

33-
public async Task<IDisposable> OnConnect(Func<USBDevice, Task> callback)
45+
[JSInvokable("OnConnect")]
46+
public Task Connected(USBDevice device)
3447
{
35-
if (callback == null) throw new ArgumentNullException(nameof(callback));
48+
if (this.OnConnect != null &&
49+
this.OnConnect.GetInvocationList().Length > 0)
50+
{
51+
this.OnConnect.Invoke(device);
52+
}
53+
return Task.CompletedTask;
54+
}
3655

37-
var callbackReference = new OnConnectCallback(Guid.NewGuid().ToString(), callback);
38-
await JSRuntime.Current.InvokeAsync<object>(ADD_ON_CONNECT_METHOD, new DotNetObjectRef(callbackReference));
39-
return callbackReference;
56+
[JSInvokable("OnDisconnect")]
57+
public Task Disconnected(USBDevice device)
58+
{
59+
if (this.OnDisconnect != null &&
60+
this.OnDisconnect.GetInvocationList().Length > 0)
61+
{
62+
this.OnDisconnect.Invoke(device);
63+
}
64+
return Task.CompletedTask;
4065
}
66+
67+
// TODO: Find out a more smart way to register to global connect/disconnect events from the WebUSB API regardless if there are subscribers to the events.
68+
public Task Initialize() => JSRuntime.Current.InvokeAsync<object>(REGISTER_USB_METHOD, new DotNetObjectRef(this));
4169
}
4270
}

src/Blazor.Extensions.WebUSB/USBAlternateInterface.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ namespace Blazor.Extensions.WebUSB
44
{
55
public class USBAlternateInterface
66
{
7-
public byte AlternateSetting { get; set; }
8-
public byte InterfaceClass { get; set; }
9-
public byte InterfaceSubclass { get; set; }
10-
public byte InterfaceProtocol { get; set; }
11-
public string InterfaceName { get; set; }
12-
public List<USBEndpoint> Endpoints { get; set; }
7+
public byte AlternateSetting { get; private set; }
8+
public byte InterfaceClass { get; private set; }
9+
public byte InterfaceSubclass { get; private set; }
10+
public byte InterfaceProtocol { get; private set; }
11+
public string InterfaceName { get; private set; }
12+
public List<USBEndpoint> Endpoints { get; private set; }
1313
}
1414
}

src/Blazor.Extensions.WebUSB/USBConfiguration.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ namespace Blazor.Extensions.WebUSB
44
{
55
public class USBConfiguration
66
{
7-
public byte ConfigurationValue { get; set; }
8-
public string ConfigurationName { get; set; }
9-
public List<USBInterface> Interfaces { get; set; }
7+
public byte ConfigurationValue { get; private set; }
8+
public string ConfigurationName { get; private set; }
9+
public List<USBInterface> Interfaces { get; private set; }
1010
}
1111
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using Microsoft.JSInterop;
5+
6+
namespace Blazor.Extensions.WebUSB
7+
{
8+
public static class USBDeviceMethods
9+
{
10+
private const string OPEN_DEVICE_METHOD = "BlazorExtensions.WebUSB.OpenDevice";
11+
private const string SELECT_CONFIGURATION_METHOD = "BlazorExtensions.WebUSB.SelectConfiguration";
12+
private const string CLAIM_INTERFACE_METHOD = "BlazorExtensions.WebUSB.ClaimInterface";
13+
14+
internal static void AttachUSB(this USBDevice device, USB usb) => device.USB = usb;
15+
public static Task<USBDevice> Open(this USBDevice device) => JSRuntime.Current.InvokeAsync<USBDevice>(OPEN_DEVICE_METHOD, device);
16+
public static Task<USBDevice> SelectConfiguration(this USBDevice device, USBConfiguration configuration)
17+
{
18+
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
19+
20+
return JSRuntime.Current.InvokeAsync<USBDevice>(SELECT_CONFIGURATION_METHOD, device, configuration);
21+
}
22+
23+
public static Task<USBDevice> ClaimInterface(this USBDevice device, USBInterface usbInterface)
24+
{
25+
if (usbInterface == null) throw new ArgumentNullException(nameof(usbInterface));
26+
27+
return JSRuntime.Current.InvokeAsync<USBDevice>(CLAIM_INTERFACE_METHOD, device, usbInterface);
28+
}
29+
30+
public static Task<USBDevice> ClaimBulkInterface(this USBDevice device)
31+
{
32+
var bulkInterface = device.Configuration.Interfaces.FirstOrDefault(i => i.Alternates.Any(a => a.Endpoints.Any(e => e.Type == USBEndpointType.Bulk)));
33+
if (bulkInterface == null) throw new InvalidOperationException("This devices doesn't have a Bulk interface");
34+
return device.ClaimInterface(bulkInterface);
35+
}
36+
}
37+
}
Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,29 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Threading.Tasks;
34
using Microsoft.JSInterop;
45

56
namespace Blazor.Extensions.WebUSB
67
{
78
public class USBDevice
89
{
9-
public byte USBVersionMajor { get; set; }
10-
public byte USBVersionMinor { get; set; }
11-
public byte USBVersionSubminor { get; set; }
12-
public byte DeviceClass { get; set; }
13-
public byte DeviceSubclass { get; set; }
14-
public byte DeviceProtocol { get; set; }
15-
public short VendorId { get; private set; }
16-
public short ProductId { get; private set; }
17-
public byte DeviceVersionMajor { get; set; }
18-
public byte DeviceVersionMinor { get; set; }
19-
public byte DeviceVersionSubminor { get; set; }
20-
public string ManufacturerName { get; set; }
21-
public string ProductName { get; set; }
22-
public string SerialNumber { get; set; }
23-
public USBConfiguration Configuration { get; set; }
24-
public List<USBConfiguration> Configurations { get; set; }
25-
public bool Opened { get; set; }
10+
internal USB USB;
11+
public byte USBVersionMajor { get; private set; }
12+
public byte USBVersionMinor { get; private set; }
13+
public byte USBVersionSubminor { get; private set; }
14+
public byte DeviceClass { get; private set; }
15+
public byte DeviceSubclass { get; private set; }
16+
public byte DeviceProtocol { get; private set; }
17+
public int VendorId { get; private set; }
18+
public int ProductId { get; private set; }
19+
public byte DeviceVersionMajor { get; private set; }
20+
public byte DeviceVersionMinor { get; private set; }
21+
public byte DeviceVersionSubminor { get; private set; }
22+
public string ManufacturerName { get; private set; }
23+
public string ProductName { get; private set; }
24+
public string SerialNumber { get; private set; }
25+
public USBConfiguration Configuration { get; private set; }
26+
public List<USBConfiguration> Configurations { get; private set; }
27+
public bool Opened { get; private set; }
2628
}
2729
}

src/Blazor.Extensions.WebUSB/USBDeviceFilter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ namespace Blazor.Extensions.WebUSB
22
{
33
public class USBDeviceFilter
44
{
5-
public short? VendorId { get; set; }
6-
public short? ProductId { get; set; }
5+
public int? VendorId { get; set; }
6+
public int? ProductId { get; set; }
77
public byte? ClassCode { get; set; }
88
public byte? SubClassCode { get; set; }
99
public byte? ProtocolCode { get; set; }

src/Blazor.Extensions.WebUSB/USBEndpoint.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ public static class USBEndpointType
1515

1616
public class USBEndpoint
1717
{
18-
public byte EndpointNumber { get; set; }
19-
public string Direction { get; set; }
20-
public string Type { get; set; }
21-
public long PacketSize { get; set; }
18+
public byte EndpointNumber { get; private set; }
19+
public string Direction { get; private set; }
20+
public string Type { get; private set; }
21+
public long PacketSize { get; private set; }
2222
}
2323
}

0 commit comments

Comments
 (0)