@v5x/serial 0.5.4 → 0.5.5

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.
Files changed (54) hide show
  1. package/README.md +72 -6
  2. package/dist/Vex.d.ts +4 -4
  3. package/dist/VexCRC.d.ts +0 -1
  4. package/dist/VexConnection.d.ts +81 -23
  5. package/dist/VexDevice.d.ts +41 -155
  6. package/{dts/VexDevice.d.ts → dist/VexDeviceState.d.ts} +63 -55
  7. package/dist/VexError.d.ts +47 -0
  8. package/dist/VexFirmware.d.ts +45 -0
  9. package/dist/VexIniConfig.d.ts +1 -1
  10. package/dist/VexPacket.d.ts +3 -512
  11. package/dist/VexPacketBase.d.ts +21 -0
  12. package/dist/VexPacketEncoder.d.ts +55 -0
  13. package/{dts/VexPacket.d.ts → dist/VexPacketModels.d.ts} +3 -107
  14. package/dist/VexPacketView.d.ts +3 -3
  15. package/dist/VexTransfers.d.ts +20 -0
  16. package/dist/index.cjs +4541 -0
  17. package/dist/index.cjs.map +25 -0
  18. package/dist/index.d.ts +9 -9
  19. package/dist/index.js +4460 -0
  20. package/dist/index.js.map +25 -0
  21. package/package.json +39 -71
  22. package/dist/v5-serial-protocol.cjs.js +0 -4516
  23. package/dist/v5-serial-protocol.cjs.js.map +0 -1
  24. package/dist/v5-serial-protocol.es.js +0 -4401
  25. package/dist/v5-serial-protocol.es.js.map +0 -1
  26. package/dts/Vex.d.ts +0 -235
  27. package/dts/Vex.js +0 -189
  28. package/dts/Vex.js.map +0 -1
  29. package/dts/VexCRC.d.ts +0 -19
  30. package/dts/VexCRC.js +0 -89
  31. package/dts/VexCRC.js.map +0 -1
  32. package/dts/VexConnection.d.ts +0 -45
  33. package/dts/VexConnection.js +0 -500
  34. package/dts/VexConnection.js.map +0 -1
  35. package/dts/VexDevice.js +0 -944
  36. package/dts/VexDevice.js.map +0 -1
  37. package/dts/VexEvent.d.ts +0 -16
  38. package/dts/VexEvent.js +0 -47
  39. package/dts/VexEvent.js.map +0 -1
  40. package/dts/VexFirmwareVersion.d.ts +0 -55
  41. package/dts/VexFirmwareVersion.js +0 -104
  42. package/dts/VexFirmwareVersion.js.map +0 -1
  43. package/dts/VexIniConfig.d.ts +0 -27
  44. package/dts/VexIniConfig.js +0 -122
  45. package/dts/VexIniConfig.js.map +0 -1
  46. package/dts/VexPacket.js +0 -1036
  47. package/dts/VexPacket.js.map +0 -1
  48. package/dts/VexPacketView.d.ts +0 -18
  49. package/dts/VexPacketView.js +0 -84
  50. package/dts/VexPacketView.js.map +0 -1
  51. package/dts/index.d.ts +0 -9
  52. package/dts/index.js +0 -10
  53. package/dts/index.js.map +0 -1
  54. package/index.d.ts +0 -1098
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  TypeScript implementation of the VEX V5 serial protocol.
4
4
 
5
+ See the [serial library documentation](https://docs.v5x.dev/serial/overview) for browser setup, guides, and high-level API reference.
6
+
5
7
  This package provides helpers for connecting to V5 devices over the Web Serial
6
8
  API, reading device state, transferring files, and working with protocol
7
9
  packets.
@@ -14,16 +16,27 @@ bun add @v5x/serial
14
16
 
15
17
  ## Usage
16
18
 
19
+ Every public async API returns a `neverthrow` `ResultAsync` whose error channel
20
+ is a `VexSerialError`. Inspect the result with `.isOk()` / `.isErr()` or
21
+ `.match()` instead of using `try`/`catch`:
22
+
17
23
  ```ts
18
24
  import { V5SerialConnection } from "@v5x/serial";
19
25
 
20
26
  const connection = new V5SerialConnection(navigator.serial);
21
27
 
22
- const connected = await connection.open();
28
+ const opened = await connection.open();
29
+ if (opened.isErr()) {
30
+ console.error(opened.error); // VexSerialError with a stable .kind
31
+ return;
32
+ }
33
+ // opened.value: true | false | undefined (opened / busy / no port selected)
23
34
 
24
- if (connected) {
25
- const status = await connection.getSystemStatus();
26
- console.log(status);
35
+ const status = await connection.getSystemStatus();
36
+ if (status.isOk()) {
37
+ console.log(status.value);
38
+ } else {
39
+ console.error(status.error.kind, status.error.message);
27
40
  }
28
41
  ```
29
42
 
@@ -31,20 +44,73 @@ Web Serial is browser-only and requires a secure context, such as HTTPS or
31
44
  `localhost`. The user must grant access to a serial device before the connection
32
45
  can open.
33
46
 
47
+ The package ships ESM, CommonJS, and TypeScript declarations. Packet and version
48
+ utilities work in Bun or Node.js, but direct browser device connections require
49
+ the Web Serial API. The `@v5x/cli` package is separate and currently supports
50
+ Linux only.
51
+
34
52
  ## Common Exports
35
53
 
36
54
  - `V5SerialConnection` for opening and managing a serial connection.
37
55
  - `V5SerialDevice` for higher-level device operations.
56
+ - `VexSerialError` and its subclasses (`VexNotConnectedError`,
57
+ `VexProtocolError`, `VexTransferError`, `VexDownloadError`,
58
+ `VexFirmwareError`, `VexIoError`, `VexInvalidArgumentError`) for typed failure
59
+ handling. Each carries a stable `kind` discriminator.
38
60
  - `PacketEncoder`, `DeviceBoundPacket`, and `HostBoundPacket` for low-level
39
61
  protocol work.
40
62
  - `ProgramIniConfig` for creating VEX program metadata.
41
63
  - Protocol enums and types from `Vex.ts`.
42
64
 
65
+ ## Result-returning async APIs
66
+
67
+ All public async methods return `ResultAsync<T, VexSerialError>` (or
68
+ `ResultAsync<T, VexSerialError>`-yielding helpers) so failures are explicit in
69
+ the type system rather than thrown. State-changing operations are awaitable and
70
+ report errors through the result channel:
71
+
72
+ ```ts
73
+ const r = await device.setMatchMode("disabled");
74
+ if (r.isErr()) console.error(r.error);
75
+
76
+ await device.brain.setActiveProgram(1).mapErr((e) => console.error(e));
77
+ await device.brain.setActiveProgram(0);
78
+ ```
79
+
80
+ The former `device.matchMode = value` and
81
+ `device.brain.activeProgram = value` setters were fire-and-forget and have been
82
+ removed. Migrate to the corresponding `setMatchMode()` / `setActiveProgram()`
83
+ methods and handle the returned `Result`.
84
+
85
+ Legacy methods that resolved to `false`, `null`, or `undefined` on failure now
86
+ return a typed `Err` instead, preserving the prior success value semantics on
87
+ the `Ok` channel (e.g. `open()` still resolves to `Ok(true | false |
88
+ undefined)`).
89
+
90
+ ## Transfers and timeouts
91
+
92
+ Bulk uploads and downloads on one `V5SerialConnection` are queued and run one at
93
+ a time. Await each high-level transfer; do not interleave manual low-level file
94
+ packets. A `V5SerialDevice` pauses its background refresh while a high-level
95
+ transfer is active by default.
96
+
97
+ Protocol requests default to a 1,000 ms response timeout. Individual transfer
98
+ phases use longer timeouts where erase, write, or exit operations need them.
99
+ `reconnect(0)` waits indefinitely, while a positive reconnect timeout is a total
100
+ deadline in milliseconds. A timeout or NACK is reported as a `VexProtocolError`
101
+ (or a related `VexSerialError` subclass) through the `Result` error channel; it
102
+ does not cancel work already running on the device.
103
+
104
+ User QSPI files are limited to `USER_FLASH_MAX_FILE_SIZE` (2 MiB). Firmware
105
+ updates download an archive containing non-empty `BOOT.bin` and `assets.bin`
106
+ images whose sizes must match the archive metadata. Firmware writes can render a
107
+ device unusable if power or communication is interrupted; keep them out of
108
+ normal application flows and validate the target VEXos version first.
109
+
43
110
  ## Build
44
111
 
45
112
  ```sh
46
113
  bun run build
47
114
  ```
48
115
 
49
- The build emits JavaScript bundles to `dist/` and TypeScript declarations to
50
- `dts/` and `index.d.ts`.
116
+ The build emits JavaScript bundles and TypeScript declarations under `dist/`.
package/dist/Vex.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { type VexFirmwareVersion } from "./VexFirmwareVersion";
2
- import { type HostBoundPacket } from "./VexPacket";
1
+ import { type VexFirmwareVersion } from "./VexFirmwareVersion.js";
2
+ import { type HostBoundPacket } from "./VexPacket.js";
3
3
  export declare const USER_PROG_CHUNK_SIZE = 4096;
4
4
  export declare const USER_FLASH_START = 50331648;
5
5
  export declare const USER_FLASH_SYS_CODE_START = 54525952;
@@ -86,8 +86,8 @@ export declare enum FileDownloadTarget {
86
86
  FILE_TARGET_VBUF = 3,
87
87
  FILE_TARGET_DDRC = 4,
88
88
  FILE_TARGET_DDRE = 5,
89
- FILE_TARGET_FLASH = 6,
90
- FILE_TARGET_RADIO = 7,
89
+ FILE_TARGET_FLASH = 6,// for IQ2
90
+ FILE_TARGET_RADIO = 7,// for IQ2
91
91
  FILE_TARGET_A1 = 13,
92
92
  FILE_TARGET_B1 = 14,
93
93
  FILE_TARGET_B2 = 15
package/dist/VexCRC.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  export declare class CrcGenerator {
2
- crc16Table: Uint32Array;
3
2
  crc32Table: Uint32Array;
4
3
  static POLYNOMIAL_CRC32: number;
5
4
  static POLYNOMIAL_CRC16: number;
@@ -1,9 +1,10 @@
1
- /// <reference types="dom-serial" />
2
- import { AckType, FileDownloadTarget, type IFileBasicInfo, type IFileWriteRequest, type IPacketCallback, type MatchMode, type SlotNumber, type SelectDashScreen } from "./Vex";
3
- import { VexEventTarget } from "./VexEvent";
4
- import { type ProgramIniConfig } from "./VexIniConfig";
5
- import { MatchStatusReplyD2HPacket, DeviceBoundPacket, MatchModeReplyD2HPacket, GetSystemStatusReplyD2HPacket, type HostBoundPacket, Query1ReplyD2HPacket, LoadFileActionReplyD2HPacket, GetSystemFlagsReplyD2HPacket, GetRadioStatusReplyD2HPacket, GetDeviceStatusReplyD2HPacket, SendDashTouchReplyD2HPacket, type SelectDashReplyD2HPacket } from "./VexPacket";
6
- import { type VexFirmwareVersion } from "./VexFirmwareVersion";
1
+ import { AckType, FileDownloadTarget, type IFileBasicInfo, type IFileWriteRequest, type IPacketCallback, type MatchMode, type SlotNumber, type SelectDashScreen } from "./Vex.js";
2
+ import { VexSerialError } from "./VexError.js";
3
+ import { VexEventTarget } from "./VexEvent.js";
4
+ import { type ProgramIniConfig } from "./VexIniConfig.js";
5
+ import { Result, ResultAsync } from "neverthrow";
6
+ import { MatchStatusReplyD2HPacket, DeviceBoundPacket, MatchModeReplyD2HPacket, GetSystemStatusReplyD2HPacket, type HostBoundPacket, Query1ReplyD2HPacket, LoadFileActionReplyD2HPacket, GetSystemFlagsReplyD2HPacket, GetRadioStatusReplyD2HPacket, GetDeviceStatusReplyD2HPacket, SendDashTouchReplyD2HPacket, SelectDashReplyD2HPacket, ScreenCaptureReplyD2HPacket } from "./VexPacket.js";
7
+ import { type VexFirmwareVersion } from "./VexFirmwareVersion.js";
7
8
  /**
8
9
  * A connection to a V5 device.
9
10
  * Emit events: connected, disconnected
@@ -15,31 +16,88 @@ export declare class VexSerialConnection extends VexEventTarget {
15
16
  port: SerialPort | undefined;
16
17
  serial: Serial;
17
18
  callbacksQueue: IPacketCallback[];
19
+ private _onPortDisconnect;
20
+ private _closePromise;
21
+ private _wasConnected;
22
+ protected fileTransferTail: Promise<unknown>;
23
+ protected fileTransferDepth: number;
18
24
  get isConnected(): boolean;
25
+ get isFileTransferring(): boolean;
19
26
  constructor(serial: Serial);
20
27
  close(): Promise<void>;
21
- open(use?: number | undefined, askUser?: boolean): Promise<boolean | undefined>;
28
+ private _hasOpenResources;
29
+ private _doClose;
30
+ /**
31
+ * Open a port. The result is `Err` only when a connection is already
32
+ * open (programmer error); otherwise the underlying `boolean |
33
+ * undefined` semantics are preserved: `true` = opened, `false` =
34
+ * the port was busy, `undefined` = no matching port was selected.
35
+ */
36
+ open(use?: number | undefined, askUser?: boolean): ResultAsync<boolean | undefined, VexSerialError>;
37
+ private _open;
22
38
  writeData(rawData: DeviceBoundPacket | Uint8Array, resolve: (data: HostBoundPacket | ArrayBuffer | AckType) => void, timeout?: number): void;
23
39
  writeDataAsync(rawData: DeviceBoundPacket | Uint8Array, timeout?: number): Promise<HostBoundPacket | ArrayBuffer | AckType>;
24
40
  protected readData(cache: Uint8Array, expectedSize: number): Promise<Uint8Array>;
25
41
  protected startReader(): Promise<void>;
26
- query1(): Promise<Query1ReplyD2HPacket | null>;
27
- getSystemVersion(): Promise<VexFirmwareVersion | null>;
42
+ query1(): ResultAsync<Query1ReplyD2HPacket, VexSerialError>;
43
+ getSystemVersion(): ResultAsync<VexFirmwareVersion, VexSerialError>;
28
44
  }
29
45
  export declare class V5SerialConnection extends VexSerialConnection {
30
46
  filters: SerialPortFilter[];
31
- getDeviceStatus(): Promise<GetDeviceStatusReplyD2HPacket | null>;
32
- getRadioStatus(): Promise<GetRadioStatusReplyD2HPacket | null>;
33
- getSystemFlags(): Promise<GetSystemFlagsReplyD2HPacket | null>;
34
- getSystemStatus(timeout?: number): Promise<GetSystemStatusReplyD2HPacket | null>;
35
- getMatchStatus(): Promise<MatchStatusReplyD2HPacket | null>;
36
- uploadProgramToDevice(iniConfig: ProgramIniConfig, binFileBuf: Uint8Array, coldFileBuf: Uint8Array | undefined, progressCallback: (state: string, current: number, total: number) => void): Promise<boolean | undefined>;
37
- downloadFileToHost(request: IFileBasicInfo, downloadTarget?: FileDownloadTarget, progressCallback?: (current: number, total: number) => void): Promise<Uint8Array>;
38
- uploadFileToDevice(request: IFileWriteRequest, progressCallback?: (current: number, total: number) => void): Promise<boolean>;
39
- setMatchMode(mode: MatchMode): Promise<MatchModeReplyD2HPacket | null>;
40
- loadProgram(value: SlotNumber | string): Promise<LoadFileActionReplyD2HPacket | null>;
41
- stopProgram(): Promise<LoadFileActionReplyD2HPacket | null>;
42
- mockTouch(x: number, y: number, press: boolean): Promise<SendDashTouchReplyD2HPacket | null>;
43
- /** @param port untested */
44
- openScreen(screen: number | SelectDashScreen, port: number): Promise<SelectDashReplyD2HPacket | null>;
47
+ /**
48
+ * Serialize every transfer that touches the device's file-transfer mode
49
+ * through a single connection-level queue. Each call returns the prior
50
+ * tail and chains after it, so transfers always execute in request
51
+ * order without packet interleaving.
52
+ */
53
+ withFileTransfer<T>(operation: () => Promise<T>): Promise<T>;
54
+ getDeviceStatus(): ResultAsync<GetDeviceStatusReplyD2HPacket, VexSerialError>;
55
+ getRadioStatus(): ResultAsync<GetRadioStatusReplyD2HPacket, VexSerialError>;
56
+ getSystemFlags(): ResultAsync<GetSystemFlagsReplyD2HPacket, VexSerialError>;
57
+ getSystemStatus(timeout?: number): ResultAsync<GetSystemStatusReplyD2HPacket, VexSerialError>;
58
+ getMatchStatus(): ResultAsync<MatchStatusReplyD2HPacket, VexSerialError>;
59
+ /**
60
+ * Upload an entire program (INI, optional cold binary, and the user
61
+ * binary) under a single connection-level transaction so that no other
62
+ * file-transfer request can interleave with the multi-file write.
63
+ */
64
+ uploadProgramToDevice(iniConfig: ProgramIniConfig, binFileBuf: Uint8Array, coldFileBuf: Uint8Array | undefined, progressCallback: (state: string, current: number, total: number) => void): ResultAsync<boolean, VexSerialError>;
65
+ private _uploadProgramToDevice;
66
+ downloadFileToHost(request: IFileBasicInfo, downloadTarget?: FileDownloadTarget, progressCallback?: (current: number, total: number) => void): ResultAsync<Uint8Array, VexSerialError>;
67
+ /**
68
+ * Run a download without acquiring the connection-level transfer lock.
69
+ * Intended for callers that already hold a transaction (such as
70
+ * `captureScreen`) and need to issue the download within a larger
71
+ * queued operation.
72
+ */
73
+ downloadFileToHostUnlocked(request: IFileBasicInfo, downloadTarget?: FileDownloadTarget, progressCallback?: (current: number, total: number) => void): ResultAsync<Uint8Array, VexSerialError>;
74
+ private _downloadFileToHostUnlocked;
75
+ uploadFileToDevice(request: IFileWriteRequest, progressCallback?: (current: number, total: number) => void): ResultAsync<boolean, VexSerialError>;
76
+ uploadFileToDeviceUnlocked(request: IFileWriteRequest, progressCallback?: (current: number, total: number) => void): Promise<Result<boolean, VexSerialError>>;
77
+ /**
78
+ * Erase a single file under a single transfer-mode session, exiting
79
+ * file-transfer mode in a `finally` block regardless of how the
80
+ * operation completes.
81
+ */
82
+ removeFile(request: IFileBasicInfo | string): ResultAsync<void, VexSerialError>;
83
+ /**
84
+ * Erase every file in the user vendor namespace under a single
85
+ * transfer-mode session.
86
+ */
87
+ removeAllFiles(): ResultAsync<void, VexSerialError>;
88
+ /**
89
+ * Issue the screen-capture command and validate that the device
90
+ * acknowledged it. Callers must inspect the returned packet (or the
91
+ * error result on NACK) before downloading the framebuffer so that a
92
+ * rejected request performs no download.
93
+ */
94
+ captureScreenSetup(): ResultAsync<ScreenCaptureReplyD2HPacket, VexSerialError>;
95
+ captureScreen(progressCallback?: (current: number, total: number) => void): ResultAsync<Uint8Array, VexSerialError>;
96
+ private _captureScreen;
97
+ setMatchMode(mode: MatchMode): ResultAsync<MatchModeReplyD2HPacket, VexSerialError>;
98
+ runProgram(value: SlotNumber | string): ResultAsync<LoadFileActionReplyD2HPacket, VexSerialError>;
99
+ loadProgram(value: SlotNumber | string): ResultAsync<LoadFileActionReplyD2HPacket, VexSerialError>;
100
+ stopProgram(): ResultAsync<LoadFileActionReplyD2HPacket, VexSerialError>;
101
+ mockTouch(x: number, y: number, press: boolean): ResultAsync<SendDashTouchReplyD2HPacket, VexSerialError>;
102
+ openScreen(screen: number | SelectDashScreen, port: number): ResultAsync<SelectDashReplyD2HPacket, VexSerialError>;
45
103
  }
@@ -1,158 +1,20 @@
1
- /// <reference types="dom-serial" />
2
- import { type MatchMode, SerialDeviceType, type ISmartDeviceInfo, SmartDeviceType, FileVendor, type IProgramInfo, type IFileHandle, FileDownloadTarget, type IFileBasicInfo, type IFileWriteRequest, RadioChannelType } from "./Vex";
3
- import { V5SerialConnection } from "./VexConnection";
4
- import { VexEventTarget } from "./VexEvent";
5
- import { VexFirmwareVersion } from "./VexFirmwareVersion";
6
- import { type ProgramIniConfig } from "./VexIniConfig";
7
- export declare function downloadFileFromInternet(link: string): Promise<ArrayBuffer>;
8
- export declare function sleepUntilAsync(f: () => Promise<boolean>, timeout: number, interval?: number): Promise<boolean>;
9
- export declare function sleepUntil(f: () => boolean, timeout: number, interval?: number): Promise<boolean>;
10
- export declare function sleep(ms: number): Promise<unknown>;
11
- export declare abstract class VexSerialDevice extends VexEventTarget {
12
- connection: V5SerialConnection | undefined;
13
- defaultSerial: Serial;
14
- get isConnected(): boolean;
15
- get deviceType(): SerialDeviceType | undefined;
16
- constructor(defaultSerial: Serial);
17
- abstract connect(conn?: V5SerialConnection): Promise<boolean>;
18
- abstract disconnect(): void;
19
- }
20
- declare class V5SerialDeviceState {
21
- _instance: V5SerialDevice;
22
- _isFileTransferring: boolean;
23
- brain: {
24
- activeProgram: number;
25
- battery: {
26
- batteryPercent: number;
27
- isCharging: boolean;
28
- };
29
- button: {
30
- isPressed: boolean;
31
- isDoublePressed: boolean;
32
- };
33
- cpu0Version: VexFirmwareVersion;
34
- cpu1Version: VexFirmwareVersion;
35
- isAvailable: boolean;
36
- settings: {
37
- isScreenReversed: boolean;
38
- isWhiteTheme: boolean;
39
- usingLanguage: number;
40
- };
41
- systemVersion: VexFirmwareVersion;
42
- uniqueId: number;
43
- };
44
- controllers: ({
45
- battery: number;
46
- isAvailable: boolean;
47
- isCharging: boolean;
48
- } | {
49
- battery: number;
50
- isAvailable: boolean;
51
- isCharging?: undefined;
52
- })[];
53
- devices: Array<ISmartDeviceInfo | undefined>;
54
- isFieldControllerConnected: boolean;
55
- matchMode: MatchMode;
56
- radio: {
57
- channel: number;
58
- isAvailable: boolean;
59
- isConnected: boolean;
60
- isVexNet: boolean;
61
- isRadioData: boolean;
62
- latency: number;
63
- signalQuality: number;
64
- signalStrength: number;
65
- };
66
- constructor(instance: V5SerialDevice);
67
- }
68
- export declare class V5Brain {
69
- private readonly state;
70
- constructor(state: V5SerialDeviceState);
71
- get isRunningProgram(): boolean;
72
- get activeProgram(): number;
73
- set activeProgram(value: number);
74
- get battery(): V5Battery;
75
- get button(): V5BrainButton;
76
- get cpu0Version(): VexFirmwareVersion;
77
- get cpu1Version(): VexFirmwareVersion;
78
- get isAvailable(): boolean;
79
- get settings(): V5BrainSettings;
80
- get systemVersion(): VexFirmwareVersion;
81
- get uniqueId(): number;
82
- getValue(key: string): Promise<string | undefined>;
83
- setValue(key: string, value: string): Promise<boolean>;
84
- listFiles(vendor?: FileVendor): Promise<IFileHandle[] | undefined>;
85
- listProgram(): Promise<IProgramInfo[] | undefined>;
86
- readFile(request: IFileBasicInfo | string, downloadTarget?: FileDownloadTarget, progressCallback?: (current: number, total: number) => void): Promise<Uint8Array | undefined>;
87
- removeFile(request: IFileBasicInfo | string): Promise<boolean | undefined>;
88
- removeAllFiles(): Promise<boolean | undefined>;
89
- uploadFirmware(publicUrl?: string, usingVersion?: string, progressCallback?: (state: string, current: number, total: number) => void): Promise<boolean | undefined>;
90
- uploadProgram(iniConfig: ProgramIniConfig, binFileBuf: Uint8Array, coldFileBuf: Uint8Array | undefined, progressCallback: (state: string, current: number, total: number) => void): Promise<boolean | undefined>;
91
- writeFile(request: IFileWriteRequest, progressCallback?: (current: number, total: number) => void): Promise<boolean | undefined>;
92
- /**
93
- *
94
- * @param progressCallback Informs the progress of the download.
95
- * @returns array of bytes where each pixel is represented by 3 consecutive bytes (rgb).
96
- * This array's length is 272 width * 480 height * 3 channels = 391680 bytes.
97
- */
98
- captureScreen(progressCallback?: (current: number, total: number) => void): Promise<Uint8Array | undefined>;
99
- }
100
- export declare class V5Battery {
101
- private readonly state;
102
- constructor(state: V5SerialDeviceState);
103
- get batteryPercent(): number;
104
- get isCharging(): boolean;
105
- }
106
- export declare class V5BrainButton {
107
- private readonly state;
108
- constructor(state: V5SerialDeviceState);
109
- get isPressed(): boolean;
110
- get isDoublePressed(): boolean;
111
- }
112
- export declare class V5BrainSettings {
113
- private readonly state;
114
- constructor(state: V5SerialDeviceState);
115
- get isScreenReversed(): boolean;
116
- get isWhiteTheme(): boolean;
117
- get usingLanguage(): number;
118
- }
119
- export declare class V5Controller {
120
- private readonly state;
121
- private readonly controllerIndex;
122
- constructor(state: V5SerialDeviceState, controllerIndex: number);
123
- get batteryPercent(): number;
124
- get isMasterController(): boolean;
125
- get isAvailable(): boolean;
126
- get isCharging(): boolean | undefined;
127
- }
128
- export declare class V5SmartDevice {
129
- private readonly state;
130
- private readonly deviceIndex;
131
- constructor(state: V5SerialDeviceState, index: number);
132
- protected getDeviceInfo(): ISmartDeviceInfo | undefined;
133
- get isAvailable(): boolean;
134
- get port(): number;
135
- get type(): SmartDeviceType;
136
- get version(): number;
137
- }
138
- export declare class V5Radio {
139
- private readonly state;
140
- constructor(state: V5SerialDeviceState);
141
- get channel(): number;
142
- get isAvailable(): boolean;
143
- get isConnected(): boolean;
144
- get isVexNet(): boolean;
145
- get isRadioData(): boolean;
146
- get latency(): number;
147
- changeChannel(channel: RadioChannelType): Promise<boolean>;
148
- }
1
+ import { type MatchMode } from "./Vex.js";
2
+ import { V5SerialConnection } from "./VexConnection.js";
3
+ import { V5Brain, V5Controller, V5Radio, V5SerialDeviceState, V5SmartDevice, VexSerialDevice } from "./VexDeviceState.js";
4
+ import { VexSerialError } from "./VexError.js";
5
+ import { ResultAsync } from "neverthrow";
6
+ export { VexSerialDevice, V5Brain, V5Battery, V5BrainButton, V5BrainSettings, V5Controller, V5SmartDevice, V5Radio, V5SerialDeviceState, } from "./VexDeviceState.js";
7
+ export { sleep, sleepUntil, sleepUntilAsync, downloadFileFromInternet, uploadFirmware, } from "./VexFirmware.js";
149
8
  export declare class V5SerialDevice extends VexSerialDevice {
150
9
  autoReconnect: boolean;
151
10
  autoRefresh: boolean;
152
11
  pauseRefreshOnFileTransfer: boolean;
153
12
  protected _isReconnecting: boolean;
13
+ private _isDisconnecting;
154
14
  private _refreshInterval;
155
15
  state: V5SerialDeviceState;
16
+ private _disposed;
17
+ private _refreshGeneration;
156
18
  constructor(defaultSerial: Serial);
157
19
  get isV5Controller(): boolean;
158
20
  get brain(): V5Brain;
@@ -160,18 +22,42 @@ export declare class V5SerialDevice extends VexSerialDevice {
160
22
  get devices(): V5SmartDevice[];
161
23
  get isFieldControllerConnected(): boolean;
162
24
  get matchMode(): MatchMode;
25
+ /**
26
+ * @deprecated Setting this property dispatches a fire-and-forget
27
+ * request whose result cannot be observed. Use {@link setMatchMode}
28
+ * instead, which returns a {@link ResultAsync} that resolves to an
29
+ * error result when the device refuses or is disconnected.
30
+ */
163
31
  set matchMode(value: MatchMode);
32
+ /**
33
+ * Update the match mode and resolve only after the device
34
+ * acknowledges the command. Resolves to an error result when the
35
+ * device NACKs, the request times out, or no connection is open.
36
+ */
37
+ setMatchMode(mode: MatchMode): ResultAsync<void, VexSerialError>;
164
38
  get radio(): V5Radio;
165
- mockTouch(x: number, y: number, press: boolean): Promise<boolean>;
166
- connect(conn?: V5SerialConnection): Promise<boolean>;
39
+ mockTouch(x: number, y: number, press: boolean): ResultAsync<void, VexSerialError>;
40
+ connect(conn?: V5SerialConnection): ResultAsync<void, VexSerialError>;
41
+ private _connect;
167
42
  disconnect(): Promise<void>;
168
43
  dispose(): Promise<void>;
169
44
  /**
170
- * @param timeout defaults to 0. If timeout is 0, then it will attempt to reconnect forever
171
- * @returns
45
+ * @param timeout defaults to 0. If timeout is 0, then it will attempt to reconnect forever.
172
46
  */
173
- reconnect(timeout?: number): Promise<boolean>;
47
+ reconnect(timeout?: number): ResultAsync<void, VexSerialError>;
48
+ private _reconnect;
49
+ private waitForReconnectToFinish;
174
50
  private doAfterConnect;
175
- refresh(): Promise<boolean>;
51
+ /**
52
+ * Refresh the high-level device snapshot. All required replies are
53
+ * collected before any public state is mutated, so callers never see
54
+ * a half-updated view. A failed or missing reply resolves to an `Ok`
55
+ * of `false` (the previous snapshot is preserved and only the
56
+ * `isAvailable` flag is updated) so transient communication loss does
57
+ * not surface as a hard error result.
58
+ */
59
+ refresh(): ResultAsync<boolean, VexSerialError>;
60
+ private _refresh;
61
+ private _buildSnapshot;
62
+ private _applySnapshotIfCurrent;
176
63
  }
177
- export {};
@@ -1,25 +1,38 @@
1
- /// <reference types="dom-serial" />
2
- import { type MatchMode, SerialDeviceType, type ISmartDeviceInfo, SmartDeviceType, FileVendor, type IProgramInfo, type IFileHandle, FileDownloadTarget, type IFileBasicInfo, type IFileWriteRequest, RadioChannelType } from "./Vex";
3
- import { V5SerialConnection } from "./VexConnection";
4
- import { VexEventTarget } from "./VexEvent";
5
- import { VexFirmwareVersion } from "./VexFirmwareVersion";
6
- import { type ProgramIniConfig } from "./VexIniConfig";
7
- export declare function downloadFileFromInternet(link: string): Promise<ArrayBuffer>;
8
- export declare function sleepUntilAsync(f: () => Promise<boolean>, timeout: number, interval?: number): Promise<boolean>;
9
- export declare function sleepUntil(f: () => boolean, timeout: number, interval?: number): Promise<boolean>;
10
- export declare function sleep(ms: number): Promise<unknown>;
1
+ import { type MatchMode, SerialDeviceType, type SlotNumber, type ISmartDeviceInfo, SmartDeviceType, FileVendor, type IProgramInfo, type IFileHandle, type IFileBasicInfo, type IFileWriteRequest, FileDownloadTarget, RadioChannelType } from "./Vex.js";
2
+ import { type V5SerialConnection } from "./VexConnection.js";
3
+ import { VexEventTarget } from "./VexEvent.js";
4
+ import { VexFirmwareVersion } from "./VexFirmwareVersion.js";
5
+ import { type ProgramIniConfig } from "./VexIniConfig.js";
6
+ import { VexSerialError } from "./VexError.js";
7
+ import { ResultAsync } from "neverthrow";
8
+ import type { V5SerialDevice } from "./VexDevice.js";
11
9
  export declare abstract class VexSerialDevice extends VexEventTarget {
12
10
  connection: V5SerialConnection | undefined;
13
11
  defaultSerial: Serial;
14
12
  get isConnected(): boolean;
15
13
  get deviceType(): SerialDeviceType | undefined;
16
14
  constructor(defaultSerial: Serial);
17
- abstract connect(conn?: V5SerialConnection): Promise<boolean>;
18
- abstract disconnect(): void;
15
+ abstract connect(conn?: V5SerialConnection): ResultAsync<void, VexSerialError>;
16
+ abstract disconnect(): Promise<void>;
19
17
  }
20
- declare class V5SerialDeviceState {
18
+ export declare class V5SerialDeviceState {
21
19
  _instance: V5SerialDevice;
22
- _isFileTransferring: boolean;
20
+ /**
21
+ * Counter used only to pause automatic refresh while a file transfer
22
+ * is in flight. This is not a mutex: serialisation of transfer
23
+ * operations lives on the {@link V5SerialConnection} transaction
24
+ * queue. The counter is exposed via {@link isFileTransferring} so the
25
+ * refresh loop can skip work during transfers.
26
+ */
27
+ private refreshPauseDepth;
28
+ get isFileTransferring(): boolean;
29
+ /**
30
+ * Increment the refresh-pause depth, run the operation, and decrement
31
+ * the depth again. The actual transfer mutex lives on the connection,
32
+ * so this method does not serialise anything; it only signals the
33
+ * device that a transfer is in progress so refresh polling is paused.
34
+ */
35
+ withFileTransfer<T>(operation: () => PromiseLike<T>): Promise<T>;
23
36
  brain: {
24
37
  activeProgram: number;
25
38
  battery: {
@@ -70,7 +83,31 @@ export declare class V5Brain {
70
83
  constructor(state: V5SerialDeviceState);
71
84
  get isRunningProgram(): boolean;
72
85
  get activeProgram(): number;
86
+ /**
87
+ * @deprecated Setting this property dispatches a fire-and-forget
88
+ * request that cannot be awaited. Use {@link setActiveProgram}
89
+ * instead, which returns a {@link ResultAsync} that resolves to an
90
+ * error result when the device refuses or is disconnected.
91
+ */
73
92
  set activeProgram(value: number);
93
+ /**
94
+ * Load a program slot on the brain, or stop the currently running
95
+ * program when called with `0`. Resolves to an error result when the
96
+ * device refuses, the request times out, or no connection is open.
97
+ */
98
+ setActiveProgram(value: SlotNumber | 0): ResultAsync<void, VexSerialError>;
99
+ /**
100
+ * Request that the brain start running the program in the given slot.
101
+ * Resolves to an error result when the device refuses, the request
102
+ * times out, or no connection is open.
103
+ */
104
+ runProgram(slot: SlotNumber | string): ResultAsync<void, VexSerialError>;
105
+ /**
106
+ * Request that the brain stop the currently running program. Resolves
107
+ * to an error result when the device refuses, the request times out,
108
+ * or no connection is open.
109
+ */
110
+ stopProgram(): ResultAsync<void, VexSerialError>;
74
111
  get battery(): V5Battery;
75
112
  get button(): V5BrainButton;
76
113
  get cpu0Version(): VexFirmwareVersion;
@@ -79,23 +116,23 @@ export declare class V5Brain {
79
116
  get settings(): V5BrainSettings;
80
117
  get systemVersion(): VexFirmwareVersion;
81
118
  get uniqueId(): number;
82
- getValue(key: string): Promise<string | undefined>;
83
- setValue(key: string, value: string): Promise<boolean>;
84
- listFiles(vendor?: FileVendor): Promise<IFileHandle[] | undefined>;
85
- listProgram(): Promise<IProgramInfo[] | undefined>;
86
- readFile(request: IFileBasicInfo | string, downloadTarget?: FileDownloadTarget, progressCallback?: (current: number, total: number) => void): Promise<Uint8Array | undefined>;
87
- removeFile(request: IFileBasicInfo | string): Promise<boolean | undefined>;
88
- removeAllFiles(): Promise<boolean | undefined>;
89
- uploadFirmware(publicUrl?: string, usingVersion?: string, progressCallback?: (state: string, current: number, total: number) => void): Promise<boolean | undefined>;
90
- uploadProgram(iniConfig: ProgramIniConfig, binFileBuf: Uint8Array, coldFileBuf: Uint8Array | undefined, progressCallback: (state: string, current: number, total: number) => void): Promise<boolean | undefined>;
91
- writeFile(request: IFileWriteRequest, progressCallback?: (current: number, total: number) => void): Promise<boolean | undefined>;
119
+ getValue(key: string): ResultAsync<string | undefined, VexSerialError>;
120
+ setValue(key: string, value: string): ResultAsync<void, VexSerialError>;
121
+ listFiles(vendor?: FileVendor): ResultAsync<IFileHandle[], VexSerialError>;
122
+ listProgram(): ResultAsync<IProgramInfo[], VexSerialError>;
123
+ readFile(request: IFileBasicInfo | string, downloadTarget?: FileDownloadTarget, progressCallback?: (current: number, total: number) => void): ResultAsync<Uint8Array, VexSerialError>;
124
+ removeFile(request: IFileBasicInfo | string): ResultAsync<void, VexSerialError>;
125
+ removeAllFiles(): ResultAsync<void, VexSerialError>;
126
+ uploadFirmware(publicUrl?: string, usingVersion?: string, progressCallback?: (state: string, current: number, total: number) => void): ResultAsync<boolean, VexSerialError>;
127
+ uploadProgram(iniConfig: ProgramIniConfig, binFileBuf: Uint8Array, coldFileBuf: Uint8Array | undefined, progressCallback: (state: string, current: number, total: number) => void): ResultAsync<boolean, VexSerialError>;
128
+ writeFile(request: IFileWriteRequest, progressCallback?: (current: number, total: number) => void): ResultAsync<boolean, VexSerialError>;
92
129
  /**
93
130
  *
94
131
  * @param progressCallback Informs the progress of the download.
95
132
  * @returns array of bytes where each pixel is represented by 3 consecutive bytes (rgb).
96
133
  * This array's length is 272 width * 480 height * 3 channels = 391680 bytes.
97
134
  */
98
- captureScreen(progressCallback?: (current: number, total: number) => void): Promise<Uint8Array | undefined>;
135
+ captureScreen(progressCallback?: (current: number, total: number) => void): ResultAsync<Uint8Array, VexSerialError>;
99
136
  }
100
137
  export declare class V5Battery {
101
138
  private readonly state;
@@ -144,34 +181,5 @@ export declare class V5Radio {
144
181
  get isVexNet(): boolean;
145
182
  get isRadioData(): boolean;
146
183
  get latency(): number;
147
- changeChannel(channel: RadioChannelType): Promise<boolean>;
148
- }
149
- export declare class V5SerialDevice extends VexSerialDevice {
150
- autoReconnect: boolean;
151
- autoRefresh: boolean;
152
- pauseRefreshOnFileTransfer: boolean;
153
- protected _isReconnecting: boolean;
154
- private _refreshInterval;
155
- state: V5SerialDeviceState;
156
- constructor(defaultSerial: Serial);
157
- get isV5Controller(): boolean;
158
- get brain(): V5Brain;
159
- get controllers(): [V5Controller, V5Controller];
160
- get devices(): V5SmartDevice[];
161
- get isFieldControllerConnected(): boolean;
162
- get matchMode(): MatchMode;
163
- set matchMode(value: MatchMode);
164
- get radio(): V5Radio;
165
- mockTouch(x: number, y: number, press: boolean): Promise<boolean>;
166
- connect(conn?: V5SerialConnection): Promise<boolean>;
167
- disconnect(): Promise<void>;
168
- dispose(): Promise<void>;
169
- /**
170
- * @param timeout defaults to 0. If timeout is 0, then it will attempt to reconnect forever
171
- * @returns
172
- */
173
- reconnect(timeout?: number): Promise<boolean>;
174
- private doAfterConnect;
175
- refresh(): Promise<boolean>;
184
+ changeChannel(channel: RadioChannelType): ResultAsync<void, VexSerialError>;
176
185
  }
177
- export {};