Skip to content

Commit fe8b711

Browse files
committed
Added device discovery and metadata
1 parent adcb0d1 commit fe8b711

File tree

14 files changed

+301
-69
lines changed

14 files changed

+301
-69
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,3 +328,5 @@ ASALocalRun/
328328

329329
# MFractors (Xamarin productivity tool) working folder
330330
.mfractor/
331+
332+
**/dist/
Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,78 @@
1+
import { USBDeviceFound, USBRequestDeviceOptions, ParseUSBDevice } from "./USBTypes";
2+
3+
type DotNetReferenceType = {
4+
invokeMethod<T>(methodIdentifier: string, ...args: any[]): T,
5+
invokeMethodAsync<T>(methodIdentifier: string, ...args: any[]): Promise<T>
6+
}
7+
18
export class USBManager {
29
private usb: any = (<any>navigator).usb;
10+
private _onConnectCallbackMap: Map<string, (event) => Promise<{}>> = new Map<string, (event) => Promise<{}>>();
311

4-
public GetDevices = () => {
5-
return this.usb.getDevices();
12+
public GetDevices = async (): Promise<USBDeviceFound[]> => {
13+
let devices = await this.usb.getDevices();
14+
let found: USBDeviceFound[] = [];
15+
devices.forEach(d => {
16+
found.push(ParseUSBDevice(d));
17+
});
18+
return found;
619
}
720

8-
public RequestDevice = async (options: any) => {
9-
console.log(options);
10-
let device = await this.usb.requestDevice(options);
11-
console.log(device);
12-
let found = { vendorId: device.vendorId, productId: device.productId };
13-
return found;
21+
public RequestDevice = (options: USBRequestDeviceOptions): Promise<USBDeviceFound> => {
22+
function isEmpty(obj) {
23+
for (var prop in obj) {
24+
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
25+
return false;
26+
}
27+
}
28+
return true;
29+
}
30+
31+
let filters: any[] = [];
32+
let reqOptions: any = undefined;
33+
34+
if (options && options != null && options.filters && options.filters != null && options.filters.length > 0) {
35+
options.filters.forEach(f => {
36+
let filter: any = {};
37+
Object.keys(f).forEach(key => {
38+
if (f[key] != null) {
39+
filter[key] = f[key];
40+
}
41+
});
42+
if (!isEmpty(filter)) {
43+
filters.push(filter);
44+
}
45+
});
46+
47+
if (filters.length > 0) {
48+
reqOptions = { filters: filters };
49+
}
50+
} else {
51+
reqOptions = { filters: [] };
52+
}
53+
54+
return new Promise((resolve, reject) => {
55+
this.usb.requestDevice(reqOptions)
56+
.then(d => {
57+
console.log(d);
58+
resolve(ParseUSBDevice(d));
59+
})
60+
.catch(err => reject(err));
61+
});
62+
}
63+
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);
70+
}
71+
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
1477
}
1578
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
export interface USBDeviceFound {
2+
usbVersionMajor: number,
3+
usbVersionMinor: number,
4+
usbVersionSubminor: number,
5+
deviceClass: number,
6+
deviceSubclass: number,
7+
deviceProtocol: number,
8+
vendorId: number,
9+
productId: number,
10+
deviceVersionMajor: number,
11+
deviceVersionMinor: number,
12+
deviceVersionSubminor: number,
13+
manufacturerName: string,
14+
productName: string,
15+
serialNumber: string,
16+
configuration: USBConfiguration,
17+
configurations: USBConfiguration[],
18+
opened: boolean
19+
}
20+
export interface USBConfiguration { configurationValue: number, configurationName: string, interfaces: USBInterface[] }
21+
export interface USBInterface { interfaceNumber: number, alternate: USBAlternateInterface | null, alternates: USBAlternateInterface[], claimed: boolean }
22+
export interface USBAlternateInterface { alternateSetting: number, interfaceClass: number, interfaceSubclass: number, interfaceProtocol: number, interfaceName: string, endpoints: USBEndpoint[] }
23+
export interface USBDeviceFilter { vendorId: number, productId: number, classCode: any, subClassCode: any, protocolCode: any, serialNumber: string }
24+
export interface USBRequestDeviceOptions { filters: USBDeviceFilter[] }
25+
export interface USBEndpoint { endpointNumber: number, direction: USBDirection, type: USBEndpointType, packetSize: number }
26+
export enum USBDirection { "in", "out" }
27+
export enum USBEndpointType { "bulk", "interrupt", "isochronous" }
28+
29+
export function ParseUSBDevice(rawDevice: any): USBDeviceFound {
30+
return {
31+
usbVersionMajor: rawDevice.usbVersionMajor,
32+
usbVersionMinor: rawDevice.usbVersionMinor,
33+
usbVersionSubminor: rawDevice.usbVersionSubminor,
34+
deviceClass: rawDevice.deviceClass,
35+
deviceSubclass: rawDevice.deviceSubclass,
36+
deviceProtocol: rawDevice.deviceProtocol,
37+
vendorId: rawDevice.vendorId,
38+
productId: rawDevice.productId,
39+
deviceVersionMajor: rawDevice.deviceVersionMajor,
40+
deviceVersionMinor: rawDevice.deviceVersionMinor,
41+
deviceVersionSubminor: rawDevice.deviceVersionSubminor,
42+
manufacturerName: rawDevice.manufacturerName,
43+
productName: rawDevice.productName,
44+
serialNumber: rawDevice.serialNumber,
45+
configuration: ParseUSBConfiguration(rawDevice.configuration),
46+
configurations: rawDevice.configurations.map(raw => ParseUSBConfiguration(raw)),
47+
opened: rawDevice.opened
48+
};
49+
}
50+
51+
function ParseUSBConfiguration(rawConfiguration: any): USBConfiguration {
52+
return {
53+
configurationValue: rawConfiguration.configurationValue,
54+
configurationName: rawConfiguration.configurationName,
55+
interfaces: rawConfiguration.interfaces.map(raw => ParseUSBInterface(raw))
56+
};
57+
}
58+
59+
function ParseUSBInterface(rawInterface: any): USBInterface {
60+
return {
61+
interfaceNumber: rawInterface.interfaceNumber,
62+
alternate: rawInterface.alternate ? ParseUSBAlternateInterface(rawInterface.alternate) : null,
63+
alternates: rawInterface.alternates.map(raw => ParseUSBAlternateInterface(raw)),
64+
claimed: rawInterface.claimed
65+
};
66+
}
67+
68+
function ParseUSBAlternateInterface(rawAlternate: any): USBAlternateInterface {
69+
return {
70+
alternateSetting: rawAlternate.alternateSetting,
71+
interfaceClass: rawAlternate.interfaceClass,
72+
interfaceSubclass: rawAlternate.interfaceSubclass,
73+
interfaceProtocol: rawAlternate.interfaceProtocol,
74+
interfaceName: rawAlternate.interfaceName,
75+
endpoints: rawAlternate.endpoints.map(raw => ParseUSBEndpoint(raw))
76+
};
77+
}
78+
79+
function ParseUSBEndpoint(rawEndpoint: any): USBEndpoint {
80+
return {
81+
endpointNumber: rawEndpoint.endpointNumber,
82+
direction: <USBDirection>rawEndpoint.direction,
83+
type: <USBEndpointType>rawEndpoint.type,
84+
packetSize: rawEndpoint.packetSize
85+
};
86+
}

src/Blazor.Extensions.WebUSB/IUSB.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ namespace Blazor.Extensions.WebUSB
55
{
66
public interface IUSB
77
{
8-
// event EventHandler OnConnect;
9-
// event EventHandler OnDisconnect;
108
Task<USBDevice[]> GetDevices();
119
Task<USBDevice> RequestDevice(USBDeviceRequestOptions options = null);
10+
Task<IDisposable> OnConnect(Func<USBDevice, Task> callback);
1211
}
1312
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Microsoft.JSInterop;
4+
5+
namespace Blazor.Extensions.WebUSB
6+
{
7+
public class OnConnectCallback : IDisposable
8+
{
9+
private const string REMOVE_ON_CONNECTED_METHOD = "BlazorExtensions.WebUSB.RemoveOnConnectHandler";
10+
private readonly Func<USBDevice, Task> _callback;
11+
public string Id { [JSInvokable(nameof(Id))]get; private set; }
12+
13+
public OnConnectCallback(string id, Func<USBDevice, Task> callback)
14+
{
15+
this._callback = callback;
16+
this.Id = id;
17+
}
18+
19+
[JSInvokable]
20+
public Task OnConnected(USBDevice device) => this._callback(device);
21+
22+
public void Dispose() => JSRuntime.Current.InvokeAsync<object>(REMOVE_ON_CONNECTED_METHOD, new DotNetObjectRef(this)).GetAwaiter().GetResult();
23+
}
24+
}

src/Blazor.Extensions.WebUSB/USB.cs

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,41 +8,35 @@ namespace Blazor.Extensions.WebUSB
88
{
99
internal class USB : IUSB
1010
{
11-
private static readonly List<_USBDeviceFilter> _emptyFilters = new List<_USBDeviceFilter>();
12-
// private const string REQUEST_DEVICE_METHOD = "navigator.usb.requestDevice";
13-
// private const string GET_DEVICE_METHOD = "navigator.usb.getDevices";
11+
private static readonly List<USBDeviceFilter> _emptyFilters = new List<USBDeviceFilter>();
1412
private const string REQUEST_DEVICE_METHOD = "BlazorExtensions.WebUSB.RequestDevice";
1513
private const string GET_DEVICE_METHOD = "BlazorExtensions.WebUSB.GetDevices";
16-
// public event EventHandler OnConnect;
14+
private const string ADD_ON_CONNECT_METHOD = "BlazorExtensions.WebUSB.AddOnConnectHandler";
1715
// public event EventHandler OnDisconnect;
1816

19-
public async Task<USBDevice[]> GetDevices() => (await JSRuntime.Current.InvokeAsync<_USBDevice[]>(GET_DEVICE_METHOD)).Select(d => new USBDevice(d)).ToArray();
17+
public Task<USBDevice[]> GetDevices() => JSRuntime.Current.InvokeAsync<USBDevice[]>(GET_DEVICE_METHOD);
2018

2119
public async Task<USBDevice> RequestDevice(USBDeviceRequestOptions options = null)
2220
{
23-
var internalOptions = new _USBDeviceRequestOptions();
24-
if (options != null)
21+
try
2522
{
26-
internalOptions.filters =
27-
options.Filters.Select(f =>
28-
new _USBDeviceFilter
29-
{
30-
classCode = f.ClassCode,
31-
productId = f.ProductId,
32-
protocolCode = f.ProtocolCode,
33-
serialNumber = f.SerialNumber,
34-
subClassCode = f.SubClassCode,
35-
vendorId = f.VendorId
36-
}).ToList();
23+
if (options == null)
24+
options = new USBDeviceRequestOptions { Filters = _emptyFilters };
25+
return await JSRuntime.Current.InvokeAsync<USBDevice>(REQUEST_DEVICE_METHOD, options);
3726
}
38-
else
27+
catch (JSException)
3928
{
40-
internalOptions.filters = _emptyFilters;
29+
return null;
4130
}
31+
}
32+
33+
public async Task<IDisposable> OnConnect(Func<USBDevice, Task> callback)
34+
{
35+
if (callback == null) throw new ArgumentNullException(nameof(callback));
4236

43-
var device = await JSRuntime.Current.InvokeAsync<_USBDevice>(REQUEST_DEVICE_METHOD, internalOptions);
44-
if (device == null) return null;
45-
return new USBDevice(device);
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;
4640
}
4741
}
4842
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System.Collections.Generic;
2+
3+
namespace Blazor.Extensions.WebUSB
4+
{
5+
public class USBAlternateInterface
6+
{
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; }
13+
}
14+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.Collections.Generic;
2+
3+
namespace Blazor.Extensions.WebUSB
4+
{
5+
public class USBConfiguration
6+
{
7+
public byte ConfigurationValue { get; set; }
8+
public string ConfigurationName { get; set; }
9+
public List<USBInterface> Interfaces { get; set; }
10+
}
11+
}
Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
11
using System;
2+
using System.Collections.Generic;
23
using Microsoft.JSInterop;
34

45
namespace Blazor.Extensions.WebUSB
56
{
67
public class USBDevice
78
{
8-
public uint VendorId { get; private set; }
9-
public uint ProductId { get; private set; }
10-
11-
internal USBDevice(_USBDevice internalDevice)
12-
{
13-
this.VendorId = internalDevice.vendorId;
14-
this.ProductId = internalDevice.productId;
15-
}
16-
}
17-
18-
internal class _USBDevice
19-
{
20-
public uint vendorId { get; set; }
21-
public uint productId { get; set; }
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; }
2226
}
2327
}

src/Blazor.Extensions.WebUSB/USBDeviceFilter.cs

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,11 @@ namespace Blazor.Extensions.WebUSB
22
{
33
public class USBDeviceFilter
44
{
5-
public ushort VendorId { get; set; }
6-
public ushort ProductId { get; set; }
7-
public byte ClassCode { get; set; }
8-
public byte SubClassCode { get; set; }
9-
public byte ProtocolCode { get; set; }
5+
public short? VendorId { get; set; }
6+
public short? ProductId { get; set; }
7+
public byte? ClassCode { get; set; }
8+
public byte? SubClassCode { get; set; }
9+
public byte? ProtocolCode { get; set; }
1010
public string SerialNumber { get; set; }
1111
}
12-
13-
internal class _USBDeviceFilter
14-
{
15-
public ushort vendorId;
16-
public ushort productId;
17-
public byte classCode;
18-
public byte subClassCode;
19-
public byte protocolCode;
20-
public string serialNumber;
21-
}
2212
}

0 commit comments

Comments
 (0)