led-matrix-controllers 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +81 -84
- package/dist/led-matrix-controllers.browser.mjs +1 -1
- package/dist/led-matrix-controllers.browser.mjs.map +1 -1
- package/dist/led-matrix-controllers.umd.min.js +2 -0
- package/dist/led-matrix-controllers.umd.min.js.map +1 -0
- package/package.json +6 -3
- package/src/HardwareControllerFactory.js +33 -33
- package/src/hardware-constants.js +41 -41
- package/src/index.js +5 -5
- package/src/supported-firmware/FrameworkComputer/inputmodule-rs/CommandAbstractionLayer.js +123 -75
- package/src/supported-firmware/FrameworkComputer/inputmodule-rs/DefaultController.js +54 -54
- package/src/supported-firmware/FrameworkComputer/inputmodule-rs/commands.js +36 -36
- package/src/supported-firmware/sigroot/FW_LED_Matrix_Firmware/CommandAbstractionLayer.js +89 -89
- package/src/supported-firmware/sigroot/FW_LED_Matrix_Firmware/SigrootController.js +50 -50
- package/src/supported-firmware/sigroot/FW_LED_Matrix_Firmware/commands.js +26 -26
- package/src/supported-firmware/vddCore/sparkle-fw16/ReportAbstractionLayer.js +115 -115
- package/src/supported-firmware/vddCore/sparkle-fw16/SparkleController.js +50 -50
- package/src/supported-firmware/vddCore/sparkle-fw16/reports.js +37 -37
- package/src/web-hid/HIDOperations.js +47 -47
- package/src/web-hid/device.js +36 -36
- package/src/web-hid/util.js +3 -3
- package/src/web-serial/PortMutex.js +58 -58
- package/src/web-serial/PortOperations.js +66 -66
- package/src/web-serial/port.js +65 -65
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
export const Reports = {
|
|
2
|
-
GLITTER_DEVICE_INFO: {
|
|
3
|
-
id: 0x01,
|
|
4
|
-
bytes: 307,
|
|
5
|
-
feature: true,
|
|
6
|
-
},
|
|
7
|
-
GLITTER_BASIC_CMD: {
|
|
8
|
-
id: 0x02,
|
|
9
|
-
bytes: 16,
|
|
10
|
-
feature: true,
|
|
11
|
-
},
|
|
12
|
-
GLITTER_GRID_PWM_CNTL: {
|
|
13
|
-
id: 0x03,
|
|
14
|
-
bytes: 306,
|
|
15
|
-
feature: true,
|
|
16
|
-
},
|
|
17
|
-
GLITTER_GRID_PWM_CNTL: {
|
|
18
|
-
id: 0x04,
|
|
19
|
-
bytes: 306,
|
|
20
|
-
feature: true,
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const Commands = {
|
|
25
|
-
GLITTER_CMD_REBOOT: 0x00,
|
|
26
|
-
GLITTER_CMD_SLEEP: 0x01,
|
|
27
|
-
GLITTER_CMD_WAKE_ON_COMMAND: 0x02,
|
|
28
|
-
GLITTER_CMD_SET_SLEEP_TIMEOUT: 0x03,
|
|
29
|
-
GLITTER_CMD_SET_GLOBAL_BRIGHTNESS: 0x04,
|
|
30
|
-
GLITTER_CMD_DRAW_PIXEL: 0x05,
|
|
31
|
-
GLITTER_CMD_DRAW_LINE: 0x06
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export const BootMode = {
|
|
35
|
-
BOOTSEL: 0x00,
|
|
36
|
-
NORMAL: 0x01,
|
|
37
|
-
}
|
|
1
|
+
export const Reports = {
|
|
2
|
+
GLITTER_DEVICE_INFO: {
|
|
3
|
+
id: 0x01,
|
|
4
|
+
bytes: 307,
|
|
5
|
+
feature: true,
|
|
6
|
+
},
|
|
7
|
+
GLITTER_BASIC_CMD: {
|
|
8
|
+
id: 0x02,
|
|
9
|
+
bytes: 16,
|
|
10
|
+
feature: true,
|
|
11
|
+
},
|
|
12
|
+
GLITTER_GRID_PWM_CNTL: {
|
|
13
|
+
id: 0x03,
|
|
14
|
+
bytes: 306,
|
|
15
|
+
feature: true,
|
|
16
|
+
},
|
|
17
|
+
GLITTER_GRID_PWM_CNTL: {
|
|
18
|
+
id: 0x04,
|
|
19
|
+
bytes: 306,
|
|
20
|
+
feature: true,
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const Commands = {
|
|
25
|
+
GLITTER_CMD_REBOOT: 0x00,
|
|
26
|
+
GLITTER_CMD_SLEEP: 0x01,
|
|
27
|
+
GLITTER_CMD_WAKE_ON_COMMAND: 0x02,
|
|
28
|
+
GLITTER_CMD_SET_SLEEP_TIMEOUT: 0x03,
|
|
29
|
+
GLITTER_CMD_SET_GLOBAL_BRIGHTNESS: 0x04,
|
|
30
|
+
GLITTER_CMD_DRAW_PIXEL: 0x05,
|
|
31
|
+
GLITTER_CMD_DRAW_LINE: 0x06
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const BootMode = {
|
|
35
|
+
BOOTSEL: 0x00,
|
|
36
|
+
NORMAL: 0x01,
|
|
37
|
+
}
|
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
import { pad } from './util.js';
|
|
2
|
-
|
|
3
|
-
export class HIDOperations {
|
|
4
|
-
constructor(device) {
|
|
5
|
-
this.#device = device;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
async send(report, data) {
|
|
9
|
-
if (data.length < report.bytes) {
|
|
10
|
-
data = pad(data, report.bytes);
|
|
11
|
-
} else if (data.length > report.bytes) {
|
|
12
|
-
throw new Error('Unable to send report: too many bytes');
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const buffer = new Uint8Array(data).buffer;
|
|
16
|
-
|
|
17
|
-
if (report.feature) {
|
|
18
|
-
await this.#device.sendFeatureReport(report.id, buffer);
|
|
19
|
-
} else {
|
|
20
|
-
await this.#device.sendReport(report.id, buffer);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async request(report) {
|
|
25
|
-
let reply = [];
|
|
26
|
-
|
|
27
|
-
if (report.feature) {
|
|
28
|
-
reply = await this.#device.receiveFeatureReport(report.id);
|
|
29
|
-
} else {
|
|
30
|
-
/*
|
|
31
|
-
* HID input reports are used for unprompted data.
|
|
32
|
-
* See WebHID `HIDInputReportEvent`
|
|
33
|
-
*/
|
|
34
|
-
throw new Error('Invalid operation');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (reply.byteLength != report.bytes) {
|
|
38
|
-
const exp = report.bytes;
|
|
39
|
-
const act = reply.byteLength;
|
|
40
|
-
console.error(`reply length=${act} (expected ${exp})`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return reply;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
#device;
|
|
47
|
-
}
|
|
1
|
+
import { pad } from './util.js';
|
|
2
|
+
|
|
3
|
+
export class HIDOperations {
|
|
4
|
+
constructor(device) {
|
|
5
|
+
this.#device = device;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async send(report, data) {
|
|
9
|
+
if (data.length < report.bytes) {
|
|
10
|
+
data = pad(data, report.bytes);
|
|
11
|
+
} else if (data.length > report.bytes) {
|
|
12
|
+
throw new Error('Unable to send report: too many bytes');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const buffer = new Uint8Array(data).buffer;
|
|
16
|
+
|
|
17
|
+
if (report.feature) {
|
|
18
|
+
await this.#device.sendFeatureReport(report.id, buffer);
|
|
19
|
+
} else {
|
|
20
|
+
await this.#device.sendReport(report.id, buffer);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async request(report) {
|
|
25
|
+
let reply = [];
|
|
26
|
+
|
|
27
|
+
if (report.feature) {
|
|
28
|
+
reply = await this.#device.receiveFeatureReport(report.id);
|
|
29
|
+
} else {
|
|
30
|
+
/*
|
|
31
|
+
* HID input reports are used for unprompted data.
|
|
32
|
+
* See WebHID `HIDInputReportEvent`
|
|
33
|
+
*/
|
|
34
|
+
throw new Error('Invalid operation');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (reply.byteLength != report.bytes) {
|
|
38
|
+
const exp = report.bytes;
|
|
39
|
+
const act = reply.byteLength;
|
|
40
|
+
console.error(`reply length=${act} (expected ${exp})`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return reply;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#device;
|
|
47
|
+
}
|
package/src/web-hid/device.js
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import { PID, VID } from '../hardware-constants.js';
|
|
2
|
-
|
|
3
|
-
const extraDevices = [];
|
|
4
|
-
|
|
5
|
-
const filters = [
|
|
6
|
-
{
|
|
7
|
-
vendorId: VID,
|
|
8
|
-
productId: PID,
|
|
9
|
-
}
|
|
10
|
-
];
|
|
11
|
-
|
|
12
|
-
export class DeviceSelectionCancelled extends Error {
|
|
13
|
-
constructor() {
|
|
14
|
-
super('User cancelled device selection.');
|
|
15
|
-
this.name = this.constructor.name;
|
|
16
|
-
this.date = new Date();
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export async function getDevice() {
|
|
21
|
-
if (extraDevices && extraDevices.length > 0) {
|
|
22
|
-
return extraDevices.pop();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
extraDevices.push(...(await navigator.hid.getDevices()));
|
|
26
|
-
if (extraDevices && extraDevices.length > 0) {
|
|
27
|
-
return extraDevices.pop();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
extraDevices.push(...(await navigator.hid.requestDevice({ filters })));
|
|
31
|
-
if (extraDevices && extraDevices.length > 0) {
|
|
32
|
-
return extraDevices.pop();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
throw new DeviceSelectionCancelled();
|
|
36
|
-
}
|
|
1
|
+
import { PID, VID } from '../hardware-constants.js';
|
|
2
|
+
|
|
3
|
+
const extraDevices = [];
|
|
4
|
+
|
|
5
|
+
const filters = [
|
|
6
|
+
{
|
|
7
|
+
vendorId: VID,
|
|
8
|
+
productId: PID,
|
|
9
|
+
}
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export class DeviceSelectionCancelled extends Error {
|
|
13
|
+
constructor() {
|
|
14
|
+
super('User cancelled device selection.');
|
|
15
|
+
this.name = this.constructor.name;
|
|
16
|
+
this.date = new Date();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function getDevice() {
|
|
21
|
+
if (extraDevices && extraDevices.length > 0) {
|
|
22
|
+
return extraDevices.pop();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
extraDevices.push(...(await navigator.hid.getDevices()));
|
|
26
|
+
if (extraDevices && extraDevices.length > 0) {
|
|
27
|
+
return extraDevices.pop();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
extraDevices.push(...(await navigator.hid.requestDevice({ filters })));
|
|
31
|
+
if (extraDevices && extraDevices.length > 0) {
|
|
32
|
+
return extraDevices.pop();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
throw new DeviceSelectionCancelled();
|
|
36
|
+
}
|
package/src/web-hid/util.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export function pad(arr, len, val=0x00) {
|
|
2
|
-
return arr.concat(new Array(len - arr.length).fill(val));
|
|
3
|
-
}
|
|
1
|
+
export function pad(arr, len, val=0x00) {
|
|
2
|
+
return arr.concat(new Array(len - arr.length).fill(val));
|
|
3
|
+
}
|
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
export class PortMutex {
|
|
2
|
-
constructor(portOperations) {
|
|
3
|
-
this.#lockName = `port-mutex-${Math.random().toString()}`;
|
|
4
|
-
this.#deduper = new Map();
|
|
5
|
-
this.#portOps = portOperations;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
async acquire(fn) {
|
|
9
|
-
const trace = new Error('created bad cb').stack;
|
|
10
|
-
|
|
11
|
-
await this.#enqueue(async () => {
|
|
12
|
-
try {
|
|
13
|
-
await fn(this.#portOps);
|
|
14
|
-
} catch (e) {
|
|
15
|
-
console.error('Error occured in anonymous callback.');
|
|
16
|
-
console.error('Original error:', e);
|
|
17
|
-
console.error('--- This callback was created at ---\n', trace);
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async acquireIdempotent(key, fn) {
|
|
23
|
-
const trace = new Error('created bad cb').stack;
|
|
24
|
-
|
|
25
|
-
if (this.#deduper.has(key)) {
|
|
26
|
-
console.info(`"${key}" request coalesced.`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
this.#deduper.set(key, async () => {
|
|
30
|
-
try {
|
|
31
|
-
await fn(this.#portOps);
|
|
32
|
-
} catch (e) {
|
|
33
|
-
console.error('Error occured in anonymous callback.');
|
|
34
|
-
console.error('Original error:', e);
|
|
35
|
-
console.error('--- This callback was created at ---\n', trace);
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
await this.#enqueue(() => this.#execDedupedOp(key));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async #enqueue(fn) {
|
|
43
|
-
// `navigator.locks` maintains queue and provides mutual exclusion.
|
|
44
|
-
return navigator.locks.request(this.#lockName, fn);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async #execDedupedOp(key) {
|
|
48
|
-
if (this.#deduper.has(key)) {
|
|
49
|
-
const fn = this.#deduper.get(key);
|
|
50
|
-
this.#deduper.delete(key);
|
|
51
|
-
await fn();
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
#lockName;
|
|
56
|
-
#deduper;
|
|
57
|
-
#portOps;
|
|
58
|
-
}
|
|
1
|
+
export class PortMutex {
|
|
2
|
+
constructor(portOperations) {
|
|
3
|
+
this.#lockName = `port-mutex-${Math.random().toString()}`;
|
|
4
|
+
this.#deduper = new Map();
|
|
5
|
+
this.#portOps = portOperations;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async acquire(fn) {
|
|
9
|
+
const trace = new Error('created bad cb').stack;
|
|
10
|
+
|
|
11
|
+
await this.#enqueue(async () => {
|
|
12
|
+
try {
|
|
13
|
+
await fn(this.#portOps);
|
|
14
|
+
} catch (e) {
|
|
15
|
+
console.error('Error occured in anonymous callback.');
|
|
16
|
+
console.error('Original error:', e);
|
|
17
|
+
console.error('--- This callback was created at ---\n', trace);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async acquireIdempotent(key, fn) {
|
|
23
|
+
const trace = new Error('created bad cb').stack;
|
|
24
|
+
|
|
25
|
+
if (this.#deduper.has(key)) {
|
|
26
|
+
console.info(`"${key}" request coalesced.`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.#deduper.set(key, async () => {
|
|
30
|
+
try {
|
|
31
|
+
await fn(this.#portOps);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
console.error('Error occured in anonymous callback.');
|
|
34
|
+
console.error('Original error:', e);
|
|
35
|
+
console.error('--- This callback was created at ---\n', trace);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
await this.#enqueue(() => this.#execDedupedOp(key));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async #enqueue(fn) {
|
|
43
|
+
// `navigator.locks` maintains queue and provides mutual exclusion.
|
|
44
|
+
return navigator.locks.request(this.#lockName, fn);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async #execDedupedOp(key) {
|
|
48
|
+
if (this.#deduper.has(key)) {
|
|
49
|
+
const fn = this.#deduper.get(key);
|
|
50
|
+
this.#deduper.delete(key);
|
|
51
|
+
await fn();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#lockName;
|
|
56
|
+
#deduper;
|
|
57
|
+
#portOps;
|
|
58
|
+
}
|
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
export class PortOperations {
|
|
2
|
-
constructor(port) {
|
|
3
|
-
this.#port = port;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
async rx(length, timeout=3000) {
|
|
7
|
-
if (this.#port === null) {
|
|
8
|
-
throw new Error('attempted RX before port initialization.');
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/*
|
|
12
|
-
* ReadableStream's built-in locking mechanism cannot be awaited or otherwise
|
|
13
|
-
* asynchronously acquired. A PortMutex must be used.
|
|
14
|
-
*/
|
|
15
|
-
if (this.#port.readable.locked) {
|
|
16
|
-
throw new Error('attempted RX while port locked.');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const response = [];
|
|
20
|
-
const reader = this.#port.readable.getReader();
|
|
21
|
-
const timeoutHandle = setTimeout(() => reader.cancel(), timeout);
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
// Response may be divided across multiple reads
|
|
25
|
-
while (response.length < length) {
|
|
26
|
-
const { value, done } = await reader.read();
|
|
27
|
-
response.push(...(value ?? []));
|
|
28
|
-
if (done || !value) break;
|
|
29
|
-
}
|
|
30
|
-
} finally {
|
|
31
|
-
clearTimeout(timeoutHandle);
|
|
32
|
-
reader.releaseLock();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return response;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async tx(buffer) {
|
|
39
|
-
if (this.#port === null) {
|
|
40
|
-
throw new Error('attempted TX before port initialization.');
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/*
|
|
44
|
-
* WritableStream's built-in locking mechanism cannot be awaited or otherwise
|
|
45
|
-
* asynchronously acquired. A PortMutex must be used.
|
|
46
|
-
*/
|
|
47
|
-
if (this.#port.writable.locked) {
|
|
48
|
-
throw new Error('attempted TX while port locked.');
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const writer = this.#port.writable.getWriter();
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
await writer.write(new Uint8Array(buffer));
|
|
55
|
-
} finally {
|
|
56
|
-
/*
|
|
57
|
-
* The writer must be completely torn down between every single write.
|
|
58
|
-
* Many parsers can't handle delayed flushing and write coalescing.
|
|
59
|
-
* Command sequences will fail if `releaseLock()` is used here.
|
|
60
|
-
*/
|
|
61
|
-
await writer.close();
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
#port;
|
|
66
|
-
}
|
|
1
|
+
export class PortOperations {
|
|
2
|
+
constructor(port) {
|
|
3
|
+
this.#port = port;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
async rx(length, timeout=3000) {
|
|
7
|
+
if (this.#port === null) {
|
|
8
|
+
throw new Error('attempted RX before port initialization.');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* ReadableStream's built-in locking mechanism cannot be awaited or otherwise
|
|
13
|
+
* asynchronously acquired. A PortMutex must be used.
|
|
14
|
+
*/
|
|
15
|
+
if (this.#port.readable.locked) {
|
|
16
|
+
throw new Error('attempted RX while port locked.');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const response = [];
|
|
20
|
+
const reader = this.#port.readable.getReader();
|
|
21
|
+
const timeoutHandle = setTimeout(() => reader.cancel(), timeout);
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
// Response may be divided across multiple reads
|
|
25
|
+
while (response.length < length) {
|
|
26
|
+
const { value, done } = await reader.read();
|
|
27
|
+
response.push(...(value ?? []));
|
|
28
|
+
if (done || !value) break;
|
|
29
|
+
}
|
|
30
|
+
} finally {
|
|
31
|
+
clearTimeout(timeoutHandle);
|
|
32
|
+
reader.releaseLock();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return response;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async tx(buffer) {
|
|
39
|
+
if (this.#port === null) {
|
|
40
|
+
throw new Error('attempted TX before port initialization.');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/*
|
|
44
|
+
* WritableStream's built-in locking mechanism cannot be awaited or otherwise
|
|
45
|
+
* asynchronously acquired. A PortMutex must be used.
|
|
46
|
+
*/
|
|
47
|
+
if (this.#port.writable.locked) {
|
|
48
|
+
throw new Error('attempted TX while port locked.');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const writer = this.#port.writable.getWriter();
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
await writer.write(new Uint8Array(buffer));
|
|
55
|
+
} finally {
|
|
56
|
+
/*
|
|
57
|
+
* The writer must be completely torn down between every single write.
|
|
58
|
+
* Many parsers can't handle delayed flushing and write coalescing.
|
|
59
|
+
* Command sequences will fail if `releaseLock()` is used here.
|
|
60
|
+
*/
|
|
61
|
+
await writer.close();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#port;
|
|
66
|
+
}
|
package/src/web-serial/port.js
CHANGED
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
import { PID, VID } from '../hardware-constants.js';
|
|
2
|
-
|
|
3
|
-
const extraPorts = [];
|
|
4
|
-
|
|
5
|
-
const filters = [
|
|
6
|
-
{
|
|
7
|
-
usbVendorId: VID,
|
|
8
|
-
usbProductId: PID,
|
|
9
|
-
}
|
|
10
|
-
];
|
|
11
|
-
|
|
12
|
-
export class PortSelectionCancelled extends Error {
|
|
13
|
-
constructor() {
|
|
14
|
-
super('User cancelled port selection.');
|
|
15
|
-
this.name = this.constructor.name;
|
|
16
|
-
this.date = new Date();
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export class PortUnavailable extends Error {
|
|
21
|
-
constructor() {
|
|
22
|
-
super('Selected port already in use.');
|
|
23
|
-
this.name = this.constructor.name;
|
|
24
|
-
this.date = new Date();
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export async function getPort() {
|
|
29
|
-
try {
|
|
30
|
-
if (extraPorts && extraPorts.length > 0) {
|
|
31
|
-
return extraPorts.pop();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
extraPorts.push(...await navigator.serial.getPorts());
|
|
35
|
-
if (extraPorts && extraPorts.length > 0) {
|
|
36
|
-
return extraPorts.pop();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
extraPorts.push(...await navigator.serial.requestPort({ filters }));
|
|
40
|
-
if (extraPorts && extraPorts.length > 0) {
|
|
41
|
-
return extraPorts.pop();
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
} catch (e) {
|
|
45
|
-
if (e.name == 'NotFoundError') {
|
|
46
|
-
throw new PortSelectionCancelled();
|
|
47
|
-
} else if (e.name == 'InvalidStateError') {
|
|
48
|
-
throw new PortUnavailable();
|
|
49
|
-
} else {
|
|
50
|
-
throw e;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export async function close(port) {
|
|
56
|
-
try {
|
|
57
|
-
await port.close();
|
|
58
|
-
} catch(e) {
|
|
59
|
-
if (e.name != 'InvalidStateError') {
|
|
60
|
-
if (e.message != "Failed to execute 'close' on 'SerialPort': The port is already closed.") {
|
|
61
|
-
throw e;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
1
|
+
import { PID, VID } from '../hardware-constants.js';
|
|
2
|
+
|
|
3
|
+
const extraPorts = [];
|
|
4
|
+
|
|
5
|
+
const filters = [
|
|
6
|
+
{
|
|
7
|
+
usbVendorId: VID,
|
|
8
|
+
usbProductId: PID,
|
|
9
|
+
}
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export class PortSelectionCancelled extends Error {
|
|
13
|
+
constructor() {
|
|
14
|
+
super('User cancelled port selection.');
|
|
15
|
+
this.name = this.constructor.name;
|
|
16
|
+
this.date = new Date();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class PortUnavailable extends Error {
|
|
21
|
+
constructor() {
|
|
22
|
+
super('Selected port already in use.');
|
|
23
|
+
this.name = this.constructor.name;
|
|
24
|
+
this.date = new Date();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function getPort() {
|
|
29
|
+
try {
|
|
30
|
+
if (extraPorts && extraPorts.length > 0) {
|
|
31
|
+
return extraPorts.pop();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
extraPorts.push(...await navigator.serial.getPorts());
|
|
35
|
+
if (extraPorts && extraPorts.length > 0) {
|
|
36
|
+
return extraPorts.pop();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
extraPorts.push(...await navigator.serial.requestPort({ filters }));
|
|
40
|
+
if (extraPorts && extraPorts.length > 0) {
|
|
41
|
+
return extraPorts.pop();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
} catch (e) {
|
|
45
|
+
if (e.name == 'NotFoundError') {
|
|
46
|
+
throw new PortSelectionCancelled();
|
|
47
|
+
} else if (e.name == 'InvalidStateError') {
|
|
48
|
+
throw new PortUnavailable();
|
|
49
|
+
} else {
|
|
50
|
+
throw e;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function close(port) {
|
|
56
|
+
try {
|
|
57
|
+
await port.close();
|
|
58
|
+
} catch(e) {
|
|
59
|
+
if (e.name != 'InvalidStateError') {
|
|
60
|
+
if (e.message != "Failed to execute 'close' on 'SerialPort': The port is already closed.") {
|
|
61
|
+
throw e;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|