@senxor/serial-core 1.1.0
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/README.md +34 -0
- package/dist/index.d.ts +116 -0
- package/dist/index.js +932 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# @senxor/serial-core
|
|
2
|
+
|
|
3
|
+
Shared serial transport layer for Senxor.js. Official packages `@senxor/web-serial` and `@senxor/capacitor-serial` build on this module so the same framing, parsing, and register commands work everywhere.
|
|
4
|
+
|
|
5
|
+
Most applications should depend on a concrete transport (Web Serial or Capacitor) instead of this package. Use `@senxor/serial-core` when you need to plug Senxor into a new runtime by implementing a thin adapter around your serial API.
|
|
6
|
+
|
|
7
|
+
## What you get
|
|
8
|
+
|
|
9
|
+
- **`SerialTransportBase`** – Wraps any port that implements **`ISerialPort`** and exposes `@senxor/core`'s transport contract (`open` / `close`, register read/write, streaming callbacks). Pass the result into `new Senxor(transport)` from `@senxor/core`.
|
|
10
|
+
- **`ISerialPort`**, **`SerialDeviceInfo`**, **`SerialOptions`** – The minimal port surface your adapter must provide (open/close, `write`, event-style `on` for `data`, `error`, `open`, `close`, `disconnect`).
|
|
11
|
+
- **`senxorPortOptions`** – Default serial settings (115200 8N1) used when opening a port for Senxor.
|
|
12
|
+
- **`isSenxorDevice`** – Returns whether USB vendor/product IDs match a known Senxor product (same check the official transports use when listing devices).
|
|
13
|
+
- **`CommandSender`** – Lower-level helper for sending framed commands and matching ACKs; mainly useful if you extend or debug serial behavior rather than typical app code.
|
|
14
|
+
- **`utils`** – Small helpers such as **`toHexString`**, **`decodeUint8Array`**, shared with protocol tooling.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pnpm add @senxor/core @senxor/serial-core
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
You still need a real serial backend (browser Web Serial, Capacitor plugin, Node serial library, etc.) that you wrap with `ISerialPort`.
|
|
23
|
+
|
|
24
|
+
## Development (this repository)
|
|
25
|
+
|
|
26
|
+
From the monorepo root:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pnpm install
|
|
30
|
+
pnpm test --filter @senxor/serial-core
|
|
31
|
+
pnpm build --filter @senxor/serial-core
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
For how `Senxor` consumes a transport after you wire one up, see `packages/core/README.md` and an official transport package for a full example.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
interface SerialDeviceInfo {
|
|
3
|
+
vendorId: number;
|
|
4
|
+
productId: number;
|
|
5
|
+
[key: string]: any;
|
|
6
|
+
}
|
|
7
|
+
interface SerialOptions {
|
|
8
|
+
baudRate: number;
|
|
9
|
+
dataBits?: number;
|
|
10
|
+
stopBits?: number;
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
}
|
|
13
|
+
interface ISerialPort<TDeviceInfo extends SerialDeviceInfo = SerialDeviceInfo, TOptions extends SerialOptions = SerialOptions> {
|
|
14
|
+
deviceInfo: TDeviceInfo;
|
|
15
|
+
isOpen: boolean;
|
|
16
|
+
open(options?: TOptions): Promise<void>;
|
|
17
|
+
close(): Promise<void>;
|
|
18
|
+
write(data: Uint8Array): Promise<void>;
|
|
19
|
+
on(event: "data", listener: (data: Uint8Array) => void): () => void;
|
|
20
|
+
on(event: "error", listener: (error: Error) => void): () => void;
|
|
21
|
+
on(event: "open", listener: () => void): () => void;
|
|
22
|
+
on(event: "close", listener: () => void): () => void;
|
|
23
|
+
on(event: "disconnect", listener: () => void): () => void;
|
|
24
|
+
}
|
|
25
|
+
type SenxorCommand = {
|
|
26
|
+
RREG: {
|
|
27
|
+
regAddr: number;
|
|
28
|
+
};
|
|
29
|
+
WREG: {
|
|
30
|
+
regAddr: number;
|
|
31
|
+
regValue: number;
|
|
32
|
+
};
|
|
33
|
+
RRSE: {
|
|
34
|
+
regs: Record<number, number>;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
type SenxorAck = {
|
|
38
|
+
RREG: number;
|
|
39
|
+
WREG: void;
|
|
40
|
+
RRSE: Record<number, number>;
|
|
41
|
+
GFRA: {
|
|
42
|
+
header?: Uint8Array;
|
|
43
|
+
frame: Uint8Array;
|
|
44
|
+
};
|
|
45
|
+
SERR: void;
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/command-sender.d.ts
|
|
49
|
+
declare class CommandSender<TPort extends ISerialPort> {
|
|
50
|
+
private port;
|
|
51
|
+
private commandMutex;
|
|
52
|
+
private encoder;
|
|
53
|
+
private pendingCommand?;
|
|
54
|
+
private errorListener?;
|
|
55
|
+
constructor(port: TPort);
|
|
56
|
+
onError(listener: (error: SenxorTransportError$1) => void): void;
|
|
57
|
+
sendCommand<K extends keyof SenxorCommand>(type: K, command: string, timeout?: number, attempts?: number): Promise<SenxorAck[K]>;
|
|
58
|
+
resolveAck<K extends keyof SenxorCommand>(type: K, data: SenxorAck[K]): void;
|
|
59
|
+
private sendCommandWithRetry;
|
|
60
|
+
private sendCommandOnce;
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/transports.d.ts
|
|
64
|
+
declare const senxorPortOptions: SerialOptions;
|
|
65
|
+
declare class SerialTransportBase<TPort extends ISerialPort> implements ISenxorTransport {
|
|
66
|
+
private port;
|
|
67
|
+
private parser;
|
|
68
|
+
private commandSender;
|
|
69
|
+
private dataListener?;
|
|
70
|
+
private errorListener?;
|
|
71
|
+
private openListener?;
|
|
72
|
+
private closeListener?;
|
|
73
|
+
private disconnectListener?;
|
|
74
|
+
constructor(port: TPort);
|
|
75
|
+
get isOpen(): boolean;
|
|
76
|
+
get deviceInfo(): SerialDeviceInfo;
|
|
77
|
+
open(): Promise<void>;
|
|
78
|
+
close(): Promise<void>;
|
|
79
|
+
readReg(address: number): Promise<number>;
|
|
80
|
+
readRegs(addresses: number[]): Promise<Record<number, number>>;
|
|
81
|
+
writeReg(address: number, value: number): Promise<void>;
|
|
82
|
+
onData(listener: (data: SenxorRawData) => void): void;
|
|
83
|
+
onError(listener: (error: SenxorTransportError) => void): void;
|
|
84
|
+
onOpen(listener: () => void): void;
|
|
85
|
+
onClose(listener: () => void): void;
|
|
86
|
+
onDisconnect(listener: () => void): void;
|
|
87
|
+
private setupPortEventListeners;
|
|
88
|
+
private handleMessage;
|
|
89
|
+
private handlePortDisconnect;
|
|
90
|
+
private handlePortClose;
|
|
91
|
+
private handlePortOpen;
|
|
92
|
+
private handleGfraAck;
|
|
93
|
+
private handleParserError;
|
|
94
|
+
private handlePortError;
|
|
95
|
+
private handleSerrAck;
|
|
96
|
+
private handleUnknownAck;
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/utils.d.ts
|
|
100
|
+
/**
|
|
101
|
+
* Convert a number to a fixed-length uppercase hexadecimal string
|
|
102
|
+
* @param value - The number to convert (must be non-negative)
|
|
103
|
+
* @param length - The desired length of the hex string
|
|
104
|
+
* @returns The uppercase hex string padded to the specified length
|
|
105
|
+
* @throws Error if value is negative or produces more digits than length
|
|
106
|
+
*/
|
|
107
|
+
declare const toHexString: (value: number, length: number) => string;
|
|
108
|
+
/**
|
|
109
|
+
* Decode a Uint8Array to ASCII string
|
|
110
|
+
* @param buffer - The Uint8Array to decode
|
|
111
|
+
* @returns The decoded string
|
|
112
|
+
*/
|
|
113
|
+
declare const decodeUint8Array: (buffer: Uint8Array) => string;
|
|
114
|
+
declare const isSenxorDevice: (device: SerialDeviceInfo) => boolean;
|
|
115
|
+
//#endregion
|
|
116
|
+
export { CommandSender, ISerialPort, SenxorAck, SenxorCommand, SerialDeviceInfo, SerialOptions, SerialTransportBase, decodeUint8Array, isSenxorDevice, senxorPortOptions, toHexString };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,932 @@
|
|
|
1
|
+
import { Mutex } from "async-mutex";
|
|
2
|
+
import { PrefixLengthParser } from "serial-packet-parser";
|
|
3
|
+
const SENXOR_PRODUCT_ID = {
|
|
4
|
+
45058: "EVK",
|
|
5
|
+
45088: "XPRO",
|
|
6
|
+
37779: "XCAM"
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region ../core/src/error.ts
|
|
10
|
+
var SenxorTransportError = class extends Error {
|
|
11
|
+
cause;
|
|
12
|
+
constructor(message, cause) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "SenxorTransportError";
|
|
15
|
+
this.cause = cause;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region ../core/src/fields.ts
|
|
20
|
+
const FIELDS = {
|
|
21
|
+
SW_RESET: {
|
|
22
|
+
name: "SW_RESET",
|
|
23
|
+
readable: false,
|
|
24
|
+
writable: true,
|
|
25
|
+
addr: 0,
|
|
26
|
+
startBit: 0,
|
|
27
|
+
endBit: 1,
|
|
28
|
+
selfReset: true
|
|
29
|
+
},
|
|
30
|
+
DMA_TIMEOUT_ENABLE: {
|
|
31
|
+
name: "DMA_TIMEOUT_ENABLE",
|
|
32
|
+
readable: true,
|
|
33
|
+
writable: true,
|
|
34
|
+
addr: 1,
|
|
35
|
+
startBit: 0,
|
|
36
|
+
endBit: 1,
|
|
37
|
+
selfReset: false
|
|
38
|
+
},
|
|
39
|
+
TIMEOUT_PERIOD: {
|
|
40
|
+
name: "TIMEOUT_PERIOD",
|
|
41
|
+
readable: true,
|
|
42
|
+
writable: true,
|
|
43
|
+
addr: 1,
|
|
44
|
+
startBit: 1,
|
|
45
|
+
endBit: 3,
|
|
46
|
+
selfReset: false
|
|
47
|
+
},
|
|
48
|
+
STOP_HOST_XFER: {
|
|
49
|
+
name: "STOP_HOST_XFER",
|
|
50
|
+
readable: true,
|
|
51
|
+
writable: true,
|
|
52
|
+
addr: 1,
|
|
53
|
+
startBit: 3,
|
|
54
|
+
endBit: 4,
|
|
55
|
+
selfReset: true
|
|
56
|
+
},
|
|
57
|
+
REQ_RETRANSMIT: {
|
|
58
|
+
name: "REQ_RETRANSMIT",
|
|
59
|
+
readable: true,
|
|
60
|
+
writable: true,
|
|
61
|
+
addr: 25,
|
|
62
|
+
startBit: 0,
|
|
63
|
+
endBit: 1,
|
|
64
|
+
selfReset: true
|
|
65
|
+
},
|
|
66
|
+
AUTO_RETRANSMIT: {
|
|
67
|
+
name: "AUTO_RETRANSMIT",
|
|
68
|
+
readable: true,
|
|
69
|
+
writable: true,
|
|
70
|
+
addr: 25,
|
|
71
|
+
startBit: 1,
|
|
72
|
+
endBit: 2,
|
|
73
|
+
selfReset: false
|
|
74
|
+
},
|
|
75
|
+
GET_SINGLE_FRAME: {
|
|
76
|
+
name: "GET_SINGLE_FRAME",
|
|
77
|
+
readable: true,
|
|
78
|
+
writable: true,
|
|
79
|
+
addr: 177,
|
|
80
|
+
startBit: 0,
|
|
81
|
+
endBit: 1,
|
|
82
|
+
selfReset: true
|
|
83
|
+
},
|
|
84
|
+
CONTINUOUS_STREAM: {
|
|
85
|
+
name: "CONTINUOUS_STREAM",
|
|
86
|
+
readable: true,
|
|
87
|
+
writable: true,
|
|
88
|
+
addr: 177,
|
|
89
|
+
startBit: 1,
|
|
90
|
+
endBit: 2,
|
|
91
|
+
selfReset: false
|
|
92
|
+
},
|
|
93
|
+
READOUT_MODE: {
|
|
94
|
+
name: "READOUT_MODE",
|
|
95
|
+
readable: true,
|
|
96
|
+
writable: true,
|
|
97
|
+
addr: 177,
|
|
98
|
+
startBit: 2,
|
|
99
|
+
endBit: 5,
|
|
100
|
+
selfReset: false
|
|
101
|
+
},
|
|
102
|
+
NO_HEADER: {
|
|
103
|
+
name: "NO_HEADER",
|
|
104
|
+
readable: true,
|
|
105
|
+
writable: true,
|
|
106
|
+
addr: 177,
|
|
107
|
+
startBit: 5,
|
|
108
|
+
endBit: 6,
|
|
109
|
+
selfReset: false
|
|
110
|
+
},
|
|
111
|
+
ADC_ENABLE: {
|
|
112
|
+
name: "ADC_ENABLE",
|
|
113
|
+
readable: true,
|
|
114
|
+
writable: true,
|
|
115
|
+
addr: 177,
|
|
116
|
+
startBit: 7,
|
|
117
|
+
endBit: 8,
|
|
118
|
+
selfReset: false
|
|
119
|
+
},
|
|
120
|
+
FW_VERSION_MAJOR: {
|
|
121
|
+
name: "FW_VERSION_MAJOR",
|
|
122
|
+
readable: true,
|
|
123
|
+
writable: false,
|
|
124
|
+
addr: 178,
|
|
125
|
+
startBit: 4,
|
|
126
|
+
endBit: 8,
|
|
127
|
+
selfReset: false
|
|
128
|
+
},
|
|
129
|
+
FW_VERSION_MINOR: {
|
|
130
|
+
name: "FW_VERSION_MINOR",
|
|
131
|
+
readable: true,
|
|
132
|
+
writable: false,
|
|
133
|
+
addr: 178,
|
|
134
|
+
startBit: 0,
|
|
135
|
+
endBit: 4,
|
|
136
|
+
selfReset: false
|
|
137
|
+
},
|
|
138
|
+
FW_VERSION_BUILD: {
|
|
139
|
+
name: "FW_VERSION_BUILD",
|
|
140
|
+
readable: true,
|
|
141
|
+
writable: false,
|
|
142
|
+
addr: 179,
|
|
143
|
+
startBit: 0,
|
|
144
|
+
endBit: 8,
|
|
145
|
+
selfReset: false
|
|
146
|
+
},
|
|
147
|
+
FRAME_RATE_DIVIDER: {
|
|
148
|
+
name: "FRAME_RATE_DIVIDER",
|
|
149
|
+
readable: true,
|
|
150
|
+
writable: true,
|
|
151
|
+
addr: 180,
|
|
152
|
+
startBit: 0,
|
|
153
|
+
endBit: 7,
|
|
154
|
+
selfReset: false
|
|
155
|
+
},
|
|
156
|
+
SLEEP_PERIOD: {
|
|
157
|
+
name: "SLEEP_PERIOD",
|
|
158
|
+
readable: true,
|
|
159
|
+
writable: true,
|
|
160
|
+
addr: 181,
|
|
161
|
+
startBit: 0,
|
|
162
|
+
endBit: 6,
|
|
163
|
+
selfReset: false
|
|
164
|
+
},
|
|
165
|
+
PERIOD_X100: {
|
|
166
|
+
name: "PERIOD_X100",
|
|
167
|
+
readable: true,
|
|
168
|
+
writable: true,
|
|
169
|
+
addr: 181,
|
|
170
|
+
startBit: 6,
|
|
171
|
+
endBit: 7,
|
|
172
|
+
selfReset: false
|
|
173
|
+
},
|
|
174
|
+
SLEEP: {
|
|
175
|
+
name: "SLEEP",
|
|
176
|
+
readable: true,
|
|
177
|
+
writable: true,
|
|
178
|
+
addr: 181,
|
|
179
|
+
startBit: 7,
|
|
180
|
+
endBit: 8,
|
|
181
|
+
selfReset: true
|
|
182
|
+
},
|
|
183
|
+
READOUT_TOO_SLOW: {
|
|
184
|
+
name: "READOUT_TOO_SLOW",
|
|
185
|
+
readable: true,
|
|
186
|
+
writable: false,
|
|
187
|
+
addr: 182,
|
|
188
|
+
startBit: 1,
|
|
189
|
+
endBit: 2,
|
|
190
|
+
selfReset: true
|
|
191
|
+
},
|
|
192
|
+
SENXOR_IF_ERROR: {
|
|
193
|
+
name: "SENXOR_IF_ERROR",
|
|
194
|
+
readable: true,
|
|
195
|
+
writable: false,
|
|
196
|
+
addr: 182,
|
|
197
|
+
startBit: 2,
|
|
198
|
+
endBit: 3,
|
|
199
|
+
selfReset: false
|
|
200
|
+
},
|
|
201
|
+
CAPTURE_ERROR: {
|
|
202
|
+
name: "CAPTURE_ERROR",
|
|
203
|
+
readable: true,
|
|
204
|
+
writable: false,
|
|
205
|
+
addr: 182,
|
|
206
|
+
startBit: 3,
|
|
207
|
+
endBit: 4,
|
|
208
|
+
selfReset: false
|
|
209
|
+
},
|
|
210
|
+
DATA_READY: {
|
|
211
|
+
name: "DATA_READY",
|
|
212
|
+
readable: true,
|
|
213
|
+
writable: false,
|
|
214
|
+
addr: 182,
|
|
215
|
+
startBit: 4,
|
|
216
|
+
endBit: 5,
|
|
217
|
+
selfReset: false
|
|
218
|
+
},
|
|
219
|
+
BOOTING_UP: {
|
|
220
|
+
name: "BOOTING_UP",
|
|
221
|
+
readable: true,
|
|
222
|
+
writable: false,
|
|
223
|
+
addr: 182,
|
|
224
|
+
startBit: 5,
|
|
225
|
+
endBit: 6,
|
|
226
|
+
selfReset: false
|
|
227
|
+
},
|
|
228
|
+
CLK_SLOW_DOWN: {
|
|
229
|
+
name: "CLK_SLOW_DOWN",
|
|
230
|
+
readable: true,
|
|
231
|
+
writable: true,
|
|
232
|
+
addr: 183,
|
|
233
|
+
startBit: 0,
|
|
234
|
+
endBit: 1,
|
|
235
|
+
selfReset: false
|
|
236
|
+
},
|
|
237
|
+
MODULE_GAIN: {
|
|
238
|
+
name: "MODULE_GAIN",
|
|
239
|
+
readable: true,
|
|
240
|
+
writable: true,
|
|
241
|
+
addr: 185,
|
|
242
|
+
startBit: 0,
|
|
243
|
+
endBit: 4,
|
|
244
|
+
selfReset: false
|
|
245
|
+
},
|
|
246
|
+
SENXOR_TYPE: {
|
|
247
|
+
name: "SENXOR_TYPE",
|
|
248
|
+
readable: true,
|
|
249
|
+
writable: false,
|
|
250
|
+
addr: 186,
|
|
251
|
+
startBit: 0,
|
|
252
|
+
endBit: 8,
|
|
253
|
+
selfReset: false
|
|
254
|
+
},
|
|
255
|
+
MODULE_TYPE: {
|
|
256
|
+
name: "MODULE_TYPE",
|
|
257
|
+
readable: true,
|
|
258
|
+
writable: false,
|
|
259
|
+
addr: 187,
|
|
260
|
+
startBit: 0,
|
|
261
|
+
endBit: 8,
|
|
262
|
+
selfReset: false
|
|
263
|
+
},
|
|
264
|
+
MCU_TYPE: {
|
|
265
|
+
name: "MCU_TYPE",
|
|
266
|
+
readable: true,
|
|
267
|
+
writable: false,
|
|
268
|
+
addr: 51,
|
|
269
|
+
startBit: 0,
|
|
270
|
+
endBit: 8,
|
|
271
|
+
selfReset: false
|
|
272
|
+
},
|
|
273
|
+
LUT_SOURCE: {
|
|
274
|
+
name: "LUT_SOURCE",
|
|
275
|
+
readable: true,
|
|
276
|
+
writable: true,
|
|
277
|
+
addr: 188,
|
|
278
|
+
startBit: 0,
|
|
279
|
+
endBit: 1,
|
|
280
|
+
selfReset: false
|
|
281
|
+
},
|
|
282
|
+
LUT_SELECTOR: {
|
|
283
|
+
name: "LUT_SELECTOR",
|
|
284
|
+
readable: true,
|
|
285
|
+
writable: true,
|
|
286
|
+
addr: 188,
|
|
287
|
+
startBit: 1,
|
|
288
|
+
endBit: 3,
|
|
289
|
+
selfReset: false
|
|
290
|
+
},
|
|
291
|
+
LUT_VERSION: {
|
|
292
|
+
name: "LUT_VERSION",
|
|
293
|
+
readable: true,
|
|
294
|
+
writable: false,
|
|
295
|
+
addr: 188,
|
|
296
|
+
startBit: 4,
|
|
297
|
+
endBit: 8,
|
|
298
|
+
selfReset: false
|
|
299
|
+
},
|
|
300
|
+
CORR_FACTOR: {
|
|
301
|
+
name: "CORR_FACTOR",
|
|
302
|
+
readable: true,
|
|
303
|
+
writable: true,
|
|
304
|
+
addr: 194,
|
|
305
|
+
startBit: 0,
|
|
306
|
+
endBit: 8,
|
|
307
|
+
selfReset: false
|
|
308
|
+
},
|
|
309
|
+
START_COLOFFS_CALIB: {
|
|
310
|
+
name: "START_COLOFFS_CALIB",
|
|
311
|
+
readable: true,
|
|
312
|
+
writable: true,
|
|
313
|
+
addr: 197,
|
|
314
|
+
startBit: 1,
|
|
315
|
+
endBit: 2,
|
|
316
|
+
selfReset: true
|
|
317
|
+
},
|
|
318
|
+
COLOFFS_CALIB_ON: {
|
|
319
|
+
name: "COLOFFS_CALIB_ON",
|
|
320
|
+
readable: true,
|
|
321
|
+
writable: false,
|
|
322
|
+
addr: 197,
|
|
323
|
+
startBit: 2,
|
|
324
|
+
endBit: 3,
|
|
325
|
+
selfReset: false
|
|
326
|
+
},
|
|
327
|
+
USE_SELF_CALIB: {
|
|
328
|
+
name: "USE_SELF_CALIB",
|
|
329
|
+
readable: true,
|
|
330
|
+
writable: true,
|
|
331
|
+
addr: 197,
|
|
332
|
+
startBit: 4,
|
|
333
|
+
endBit: 5,
|
|
334
|
+
selfReset: true
|
|
335
|
+
},
|
|
336
|
+
CALIB_SAMPLE_SIZE: {
|
|
337
|
+
name: "CALIB_SAMPLE_SIZE",
|
|
338
|
+
readable: true,
|
|
339
|
+
writable: true,
|
|
340
|
+
addr: 197,
|
|
341
|
+
startBit: 5,
|
|
342
|
+
endBit: 8,
|
|
343
|
+
selfReset: false
|
|
344
|
+
},
|
|
345
|
+
EMISSIVITY: {
|
|
346
|
+
name: "EMISSIVITY",
|
|
347
|
+
readable: true,
|
|
348
|
+
writable: true,
|
|
349
|
+
addr: 202,
|
|
350
|
+
startBit: 0,
|
|
351
|
+
endBit: 8,
|
|
352
|
+
selfReset: false
|
|
353
|
+
},
|
|
354
|
+
OFFSET: {
|
|
355
|
+
name: "OFFSET",
|
|
356
|
+
readable: true,
|
|
357
|
+
writable: true,
|
|
358
|
+
addr: 203,
|
|
359
|
+
startBit: 0,
|
|
360
|
+
endBit: 8,
|
|
361
|
+
selfReset: false
|
|
362
|
+
},
|
|
363
|
+
OTF: {
|
|
364
|
+
name: "OTF",
|
|
365
|
+
readable: true,
|
|
366
|
+
writable: true,
|
|
367
|
+
addr: 205,
|
|
368
|
+
startBit: 0,
|
|
369
|
+
endBit: 8,
|
|
370
|
+
selfReset: false
|
|
371
|
+
},
|
|
372
|
+
PRODUCTION_YEAR: {
|
|
373
|
+
name: "PRODUCTION_YEAR",
|
|
374
|
+
readable: true,
|
|
375
|
+
writable: false,
|
|
376
|
+
addr: 224,
|
|
377
|
+
startBit: 0,
|
|
378
|
+
endBit: 8,
|
|
379
|
+
selfReset: false
|
|
380
|
+
},
|
|
381
|
+
PRODUCTION_WEEK: {
|
|
382
|
+
name: "PRODUCTION_WEEK",
|
|
383
|
+
readable: true,
|
|
384
|
+
writable: false,
|
|
385
|
+
addr: 225,
|
|
386
|
+
startBit: 0,
|
|
387
|
+
endBit: 8,
|
|
388
|
+
selfReset: false
|
|
389
|
+
},
|
|
390
|
+
MANUF_LOCATION: {
|
|
391
|
+
name: "MANUF_LOCATION",
|
|
392
|
+
readable: true,
|
|
393
|
+
writable: false,
|
|
394
|
+
addr: 226,
|
|
395
|
+
startBit: 0,
|
|
396
|
+
endBit: 8,
|
|
397
|
+
selfReset: false
|
|
398
|
+
},
|
|
399
|
+
SERIAL_NUMBER_0: {
|
|
400
|
+
name: "SERIAL_NUMBER_0",
|
|
401
|
+
readable: true,
|
|
402
|
+
writable: false,
|
|
403
|
+
addr: 227,
|
|
404
|
+
startBit: 0,
|
|
405
|
+
endBit: 8,
|
|
406
|
+
selfReset: false
|
|
407
|
+
},
|
|
408
|
+
SERIAL_NUMBER_1: {
|
|
409
|
+
name: "SERIAL_NUMBER_1",
|
|
410
|
+
readable: true,
|
|
411
|
+
writable: false,
|
|
412
|
+
addr: 228,
|
|
413
|
+
startBit: 0,
|
|
414
|
+
endBit: 8,
|
|
415
|
+
selfReset: false
|
|
416
|
+
},
|
|
417
|
+
SERIAL_NUMBER_2: {
|
|
418
|
+
name: "SERIAL_NUMBER_2",
|
|
419
|
+
readable: true,
|
|
420
|
+
writable: false,
|
|
421
|
+
addr: 229,
|
|
422
|
+
startBit: 0,
|
|
423
|
+
endBit: 8,
|
|
424
|
+
selfReset: false
|
|
425
|
+
},
|
|
426
|
+
USER_FLASH_ENABLE: {
|
|
427
|
+
name: "USER_FLASH_ENABLE",
|
|
428
|
+
readable: true,
|
|
429
|
+
writable: true,
|
|
430
|
+
addr: 216,
|
|
431
|
+
startBit: 0,
|
|
432
|
+
endBit: 1,
|
|
433
|
+
selfReset: false
|
|
434
|
+
},
|
|
435
|
+
TEMP_UNITS: {
|
|
436
|
+
name: "TEMP_UNITS",
|
|
437
|
+
readable: true,
|
|
438
|
+
writable: true,
|
|
439
|
+
addr: 49,
|
|
440
|
+
startBit: 0,
|
|
441
|
+
endBit: 3,
|
|
442
|
+
selfReset: false
|
|
443
|
+
},
|
|
444
|
+
STARK_ENABLE: {
|
|
445
|
+
name: "STARK_ENABLE",
|
|
446
|
+
readable: true,
|
|
447
|
+
writable: true,
|
|
448
|
+
addr: 32,
|
|
449
|
+
startBit: 0,
|
|
450
|
+
endBit: 1,
|
|
451
|
+
selfReset: false
|
|
452
|
+
},
|
|
453
|
+
STARK_TYPE: {
|
|
454
|
+
name: "STARK_TYPE",
|
|
455
|
+
readable: true,
|
|
456
|
+
writable: true,
|
|
457
|
+
addr: 32,
|
|
458
|
+
startBit: 1,
|
|
459
|
+
endBit: 4,
|
|
460
|
+
selfReset: false
|
|
461
|
+
},
|
|
462
|
+
SPATIAL_KERNEL: {
|
|
463
|
+
name: "SPATIAL_KERNEL",
|
|
464
|
+
readable: true,
|
|
465
|
+
writable: true,
|
|
466
|
+
addr: 32,
|
|
467
|
+
startBit: 4,
|
|
468
|
+
endBit: 5,
|
|
469
|
+
selfReset: false
|
|
470
|
+
},
|
|
471
|
+
STARK_CUTOFF: {
|
|
472
|
+
name: "STARK_CUTOFF",
|
|
473
|
+
readable: true,
|
|
474
|
+
writable: true,
|
|
475
|
+
addr: 33,
|
|
476
|
+
startBit: 0,
|
|
477
|
+
endBit: 7,
|
|
478
|
+
selfReset: false
|
|
479
|
+
},
|
|
480
|
+
STARK_GRADIENT: {
|
|
481
|
+
name: "STARK_GRADIENT",
|
|
482
|
+
readable: true,
|
|
483
|
+
writable: true,
|
|
484
|
+
addr: 34,
|
|
485
|
+
startBit: 0,
|
|
486
|
+
endBit: 8,
|
|
487
|
+
selfReset: false
|
|
488
|
+
},
|
|
489
|
+
STARK_SCALE: {
|
|
490
|
+
name: "STARK_SCALE",
|
|
491
|
+
readable: true,
|
|
492
|
+
writable: true,
|
|
493
|
+
addr: 35,
|
|
494
|
+
startBit: 0,
|
|
495
|
+
endBit: 8,
|
|
496
|
+
selfReset: false
|
|
497
|
+
},
|
|
498
|
+
MMS_KXMS: {
|
|
499
|
+
name: "MMS_KXMS",
|
|
500
|
+
readable: true,
|
|
501
|
+
writable: true,
|
|
502
|
+
addr: 37,
|
|
503
|
+
startBit: 0,
|
|
504
|
+
endBit: 1,
|
|
505
|
+
selfReset: false
|
|
506
|
+
},
|
|
507
|
+
MMS_RA: {
|
|
508
|
+
name: "MMS_RA",
|
|
509
|
+
readable: true,
|
|
510
|
+
writable: true,
|
|
511
|
+
addr: 37,
|
|
512
|
+
startBit: 1,
|
|
513
|
+
endBit: 2,
|
|
514
|
+
selfReset: false
|
|
515
|
+
},
|
|
516
|
+
MEDIAN_ENABLE: {
|
|
517
|
+
name: "MEDIAN_ENABLE",
|
|
518
|
+
readable: true,
|
|
519
|
+
writable: true,
|
|
520
|
+
addr: 48,
|
|
521
|
+
startBit: 0,
|
|
522
|
+
endBit: 1,
|
|
523
|
+
selfReset: false
|
|
524
|
+
},
|
|
525
|
+
MEDIAN_KERNEL_SIZE: {
|
|
526
|
+
name: "MEDIAN_KERNEL_SIZE",
|
|
527
|
+
readable: true,
|
|
528
|
+
writable: true,
|
|
529
|
+
addr: 48,
|
|
530
|
+
startBit: 1,
|
|
531
|
+
endBit: 2,
|
|
532
|
+
selfReset: false
|
|
533
|
+
},
|
|
534
|
+
TEMPORAL_ENABLE: {
|
|
535
|
+
name: "TEMPORAL_ENABLE",
|
|
536
|
+
readable: true,
|
|
537
|
+
writable: true,
|
|
538
|
+
addr: 208,
|
|
539
|
+
startBit: 0,
|
|
540
|
+
endBit: 1,
|
|
541
|
+
selfReset: false
|
|
542
|
+
},
|
|
543
|
+
TEMPORAL_INIT: {
|
|
544
|
+
name: "TEMPORAL_INIT",
|
|
545
|
+
readable: true,
|
|
546
|
+
writable: true,
|
|
547
|
+
addr: 208,
|
|
548
|
+
startBit: 1,
|
|
549
|
+
endBit: 2,
|
|
550
|
+
selfReset: true
|
|
551
|
+
},
|
|
552
|
+
TEMPORAL_LSB: {
|
|
553
|
+
name: "TEMPORAL_LSB",
|
|
554
|
+
readable: true,
|
|
555
|
+
writable: true,
|
|
556
|
+
addr: 209,
|
|
557
|
+
startBit: 0,
|
|
558
|
+
endBit: 8,
|
|
559
|
+
selfReset: false
|
|
560
|
+
},
|
|
561
|
+
TEMPORAL_MSB: {
|
|
562
|
+
name: "TEMPORAL_MSB",
|
|
563
|
+
readable: true,
|
|
564
|
+
writable: true,
|
|
565
|
+
addr: 210,
|
|
566
|
+
startBit: 0,
|
|
567
|
+
endBit: 8,
|
|
568
|
+
selfReset: false
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
(() => {
|
|
572
|
+
const result = {};
|
|
573
|
+
Object.keys(FIELDS).forEach((fieldName) => {
|
|
574
|
+
const addr = FIELDS[fieldName].addr;
|
|
575
|
+
if (!result[addr]) result[addr] = [];
|
|
576
|
+
result[addr].push(fieldName);
|
|
577
|
+
});
|
|
578
|
+
return result;
|
|
579
|
+
})();
|
|
580
|
+
//#endregion
|
|
581
|
+
//#region src/command-sender.ts
|
|
582
|
+
var CommandSender = class {
|
|
583
|
+
port;
|
|
584
|
+
commandMutex;
|
|
585
|
+
encoder = new TextEncoder();
|
|
586
|
+
pendingCommand;
|
|
587
|
+
errorListener;
|
|
588
|
+
constructor(port) {
|
|
589
|
+
this.port = port;
|
|
590
|
+
this.commandMutex = new Mutex();
|
|
591
|
+
}
|
|
592
|
+
onError(listener) {
|
|
593
|
+
this.errorListener = listener;
|
|
594
|
+
}
|
|
595
|
+
async sendCommand(type, command, timeout = 2e3, attempts = 3) {
|
|
596
|
+
return this.commandMutex.runExclusive(async () => {
|
|
597
|
+
return this.sendCommandWithRetry(type, command, timeout, attempts);
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
resolveAck(type, data) {
|
|
601
|
+
if (!this.pendingCommand) {
|
|
602
|
+
const err = new SenxorTransportError(`Bare ACK received: ${type} without a corresponding command`);
|
|
603
|
+
this.errorListener?.(err);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
if (this.pendingCommand.type !== type) {
|
|
607
|
+
const err = new SenxorTransportError(`ACK type mismatch: expected ${this.pendingCommand.type}, received ${type}`);
|
|
608
|
+
this.pendingCommand.reject(err);
|
|
609
|
+
this.pendingCommand = void 0;
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
this.pendingCommand.resolve(data);
|
|
613
|
+
this.pendingCommand = void 0;
|
|
614
|
+
}
|
|
615
|
+
async sendCommandWithRetry(type, command, timeout, attempts) {
|
|
616
|
+
let lastError;
|
|
617
|
+
for (let i = 0; i < attempts; i++) try {
|
|
618
|
+
return await this.sendCommandOnce(type, command, timeout);
|
|
619
|
+
} catch (error) {
|
|
620
|
+
lastError = error;
|
|
621
|
+
}
|
|
622
|
+
throw lastError;
|
|
623
|
+
}
|
|
624
|
+
async sendCommandOnce(type, command, timeout) {
|
|
625
|
+
return new Promise((resolve, reject) => {
|
|
626
|
+
this.pendingCommand = {
|
|
627
|
+
type,
|
|
628
|
+
resolve,
|
|
629
|
+
reject
|
|
630
|
+
};
|
|
631
|
+
const timeoutId = setTimeout(() => {
|
|
632
|
+
this.pendingCommand = void 0;
|
|
633
|
+
reject(new SenxorTransportError(`Command timeout: no ACK received for ${type}`));
|
|
634
|
+
}, timeout);
|
|
635
|
+
this.port.write(this.encoder.encode(command)).catch((err) => {
|
|
636
|
+
clearTimeout(timeoutId);
|
|
637
|
+
this.pendingCommand = void 0;
|
|
638
|
+
reject(new SenxorTransportError(`Failed to write command: ${type}`, err));
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
//#endregion
|
|
644
|
+
//#region src/utils.ts
|
|
645
|
+
const decoder = new TextDecoder("ascii");
|
|
646
|
+
/**
|
|
647
|
+
* Convert a number to a fixed-length uppercase hexadecimal string
|
|
648
|
+
* @param value - The number to convert (must be non-negative)
|
|
649
|
+
* @param length - The desired length of the hex string
|
|
650
|
+
* @returns The uppercase hex string padded to the specified length
|
|
651
|
+
* @throws Error if value is negative or produces more digits than length
|
|
652
|
+
*/
|
|
653
|
+
const toHexString = (value, length) => {
|
|
654
|
+
if (value < 0) throw new Error(`Cannot convert negative value ${value} to hex string`);
|
|
655
|
+
const hex = value.toString(16).toUpperCase();
|
|
656
|
+
if (hex.length > length) throw new Error(`Value ${value} (0x${hex}) exceeds ${length} hex digits`);
|
|
657
|
+
return hex.padStart(length, "0");
|
|
658
|
+
};
|
|
659
|
+
/**
|
|
660
|
+
* Decode a Uint8Array to ASCII string
|
|
661
|
+
* @param buffer - The Uint8Array to decode
|
|
662
|
+
* @returns The decoded string
|
|
663
|
+
*/
|
|
664
|
+
const decodeUint8Array = (buffer) => {
|
|
665
|
+
return decoder.decode(buffer);
|
|
666
|
+
};
|
|
667
|
+
const isSenxorDevice = (device) => {
|
|
668
|
+
return device.vendorId === 1046 && device.productId in SENXOR_PRODUCT_ID;
|
|
669
|
+
};
|
|
670
|
+
//#endregion
|
|
671
|
+
//#region src/parser.ts
|
|
672
|
+
const msgPrefix = new TextEncoder().encode(" #");
|
|
673
|
+
const msgLenFieldSize = 4;
|
|
674
|
+
const msgLenFieldStart = 4;
|
|
675
|
+
const msgCmdStart = msgLenFieldStart + msgLenFieldSize;
|
|
676
|
+
const cmdLen = 4;
|
|
677
|
+
const checksumIndex = -4;
|
|
678
|
+
const msgDataStart = msgCmdStart + cmdLen;
|
|
679
|
+
const parseMessageLength = (buf, start, size) => {
|
|
680
|
+
const hexString = decodeUint8Array(buf.slice(start, start + size));
|
|
681
|
+
return Number.parseInt(hexString, 16);
|
|
682
|
+
};
|
|
683
|
+
const verifyChecksum = (message, _payloadStart, _payloadLen) => {
|
|
684
|
+
return decodeUint8Array(message.slice(checksumIndex)) === toHexString(message.slice(msgLenFieldStart, checksumIndex).reduce((acc, val) => acc + val, 0) & 65535, 4);
|
|
685
|
+
};
|
|
686
|
+
const parseMessageBody = (message) => {
|
|
687
|
+
return {
|
|
688
|
+
cmd: decodeUint8Array(message.slice(msgCmdStart, msgCmdStart + cmdLen)),
|
|
689
|
+
data: message.slice(msgDataStart, checksumIndex)
|
|
690
|
+
};
|
|
691
|
+
};
|
|
692
|
+
const messageParserOptions = {
|
|
693
|
+
prefix: msgPrefix,
|
|
694
|
+
lengthFieldSize: msgLenFieldSize,
|
|
695
|
+
parseLength: parseMessageLength,
|
|
696
|
+
minPayloadLen: 4,
|
|
697
|
+
maxPayloadLen: 64 * 1024,
|
|
698
|
+
maxBufferSize: 64 * 1024,
|
|
699
|
+
verifyMessage: verifyChecksum,
|
|
700
|
+
onMessage: void 0,
|
|
701
|
+
onError: void 0,
|
|
702
|
+
onResync: void 0
|
|
703
|
+
};
|
|
704
|
+
const gfraDataFormat = {
|
|
705
|
+
10080: {
|
|
706
|
+
header: void 0,
|
|
707
|
+
frame: {
|
|
708
|
+
start: 160,
|
|
709
|
+
end: 10080
|
|
710
|
+
}
|
|
711
|
+
},
|
|
712
|
+
10240: {
|
|
713
|
+
header: {
|
|
714
|
+
start: 160,
|
|
715
|
+
end: 320
|
|
716
|
+
},
|
|
717
|
+
frame: {
|
|
718
|
+
start: 320,
|
|
719
|
+
end: 10240
|
|
720
|
+
}
|
|
721
|
+
},
|
|
722
|
+
39360: {
|
|
723
|
+
header: void 0,
|
|
724
|
+
frame: {
|
|
725
|
+
start: 960,
|
|
726
|
+
end: 39360
|
|
727
|
+
}
|
|
728
|
+
},
|
|
729
|
+
39680: {
|
|
730
|
+
header: {
|
|
731
|
+
start: 960,
|
|
732
|
+
end: 1280
|
|
733
|
+
},
|
|
734
|
+
frame: {
|
|
735
|
+
start: 1280,
|
|
736
|
+
end: 39680
|
|
737
|
+
}
|
|
738
|
+
},
|
|
739
|
+
5200: {
|
|
740
|
+
header: {
|
|
741
|
+
start: 100,
|
|
742
|
+
end: 200
|
|
743
|
+
},
|
|
744
|
+
frame: {
|
|
745
|
+
start: 200,
|
|
746
|
+
end: 5200
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
const senxorAckDecoder = {
|
|
751
|
+
RREG(data) {
|
|
752
|
+
const hexString = decodeUint8Array(data);
|
|
753
|
+
return Number.parseInt(hexString, 16);
|
|
754
|
+
},
|
|
755
|
+
WREG(data) {},
|
|
756
|
+
RRSE(data) {
|
|
757
|
+
const regs = {};
|
|
758
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
759
|
+
const regAddr = Number.parseInt(decodeUint8Array(data.slice(i, i + 2)), 16);
|
|
760
|
+
regs[regAddr] = Number.parseInt(decodeUint8Array(data.slice(i + 2, i + 4)), 16);
|
|
761
|
+
}
|
|
762
|
+
return regs;
|
|
763
|
+
},
|
|
764
|
+
GFRA(data) {
|
|
765
|
+
const length = data.length;
|
|
766
|
+
if (length in gfraDataFormat) {
|
|
767
|
+
const { header: headerSlice, frame: frameSlice } = gfraDataFormat[length];
|
|
768
|
+
return {
|
|
769
|
+
header: headerSlice ? data.slice(headerSlice.start, headerSlice.end) : void 0,
|
|
770
|
+
frame: data.slice(frameSlice.start, frameSlice.end)
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
throw new Error(`Invalid GFRA data length: ${length}`);
|
|
774
|
+
},
|
|
775
|
+
SERR(data) {}
|
|
776
|
+
};
|
|
777
|
+
const senxorAckEncoder = {
|
|
778
|
+
RREG(regAddr) {
|
|
779
|
+
return ` #000ARREG${toHexString(regAddr, 2)}XXXX`;
|
|
780
|
+
},
|
|
781
|
+
WREG(regAddr, regValue) {
|
|
782
|
+
return ` #000CWREG${toHexString(regAddr, 2)}${toHexString(regValue, 2)}XXXX`;
|
|
783
|
+
},
|
|
784
|
+
RRSE(addrs) {
|
|
785
|
+
const payload = `RRSE${addrs.map((addr) => toHexString(addr, 2)).join("")}FFXXXX`;
|
|
786
|
+
return ` #${toHexString(payload.length, 4)}${payload}`;
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
//#endregion
|
|
790
|
+
//#region src/transports.ts
|
|
791
|
+
const senxorPortOptions = {
|
|
792
|
+
baudRate: 115200,
|
|
793
|
+
bytesize: 8,
|
|
794
|
+
stopbits: 1
|
|
795
|
+
};
|
|
796
|
+
var SerialTransportBase = class {
|
|
797
|
+
port;
|
|
798
|
+
parser;
|
|
799
|
+
commandSender;
|
|
800
|
+
dataListener;
|
|
801
|
+
errorListener;
|
|
802
|
+
openListener;
|
|
803
|
+
closeListener;
|
|
804
|
+
disconnectListener;
|
|
805
|
+
constructor(port) {
|
|
806
|
+
this.port = port;
|
|
807
|
+
this.commandSender = new CommandSender(this.port);
|
|
808
|
+
this.parser = new PrefixLengthParser({
|
|
809
|
+
...messageParserOptions,
|
|
810
|
+
onMessage: this.handleMessage.bind(this),
|
|
811
|
+
onError: this.handleParserError.bind(this)
|
|
812
|
+
});
|
|
813
|
+
this.setupPortEventListeners();
|
|
814
|
+
}
|
|
815
|
+
get isOpen() {
|
|
816
|
+
return this.port.isOpen;
|
|
817
|
+
}
|
|
818
|
+
get deviceInfo() {
|
|
819
|
+
return this.port.deviceInfo;
|
|
820
|
+
}
|
|
821
|
+
async open() {
|
|
822
|
+
await this.port.open(senxorPortOptions);
|
|
823
|
+
}
|
|
824
|
+
async close() {
|
|
825
|
+
await this.port.close();
|
|
826
|
+
}
|
|
827
|
+
async readReg(address) {
|
|
828
|
+
if (!this.port.isOpen) throw new SenxorTransportError("Cannot read register: port is closed");
|
|
829
|
+
const command = senxorAckEncoder.RREG(address);
|
|
830
|
+
return this.commandSender.sendCommand("RREG", command, 1e3, 2);
|
|
831
|
+
}
|
|
832
|
+
async readRegs(addresses) {
|
|
833
|
+
if (!this.port.isOpen) throw new SenxorTransportError("Cannot read registers: port is closed");
|
|
834
|
+
const commands = senxorAckEncoder.RRSE(addresses);
|
|
835
|
+
return this.commandSender.sendCommand("RRSE", commands, 1e3, 2);
|
|
836
|
+
}
|
|
837
|
+
async writeReg(address, value) {
|
|
838
|
+
if (!this.port.isOpen) throw new SenxorTransportError("Cannot write register: port is closed");
|
|
839
|
+
const command = senxorAckEncoder.WREG(address, value);
|
|
840
|
+
return this.commandSender.sendCommand("WREG", command, 1e3, 2);
|
|
841
|
+
}
|
|
842
|
+
onData(listener) {
|
|
843
|
+
this.dataListener = listener;
|
|
844
|
+
}
|
|
845
|
+
onError(listener) {
|
|
846
|
+
this.errorListener = listener;
|
|
847
|
+
this.commandSender.onError(listener);
|
|
848
|
+
}
|
|
849
|
+
onOpen(listener) {
|
|
850
|
+
this.openListener = listener;
|
|
851
|
+
}
|
|
852
|
+
onClose(listener) {
|
|
853
|
+
this.closeListener = listener;
|
|
854
|
+
}
|
|
855
|
+
onDisconnect(listener) {
|
|
856
|
+
this.disconnectListener = listener;
|
|
857
|
+
}
|
|
858
|
+
setupPortEventListeners() {
|
|
859
|
+
this.port.on("data", (data) => this.parser.push(data));
|
|
860
|
+
this.port.on("error", (error) => this.handlePortError(error));
|
|
861
|
+
this.port.on("open", () => this.handlePortOpen());
|
|
862
|
+
this.port.on("close", () => this.handlePortClose());
|
|
863
|
+
this.port.on("disconnect", () => this.handlePortDisconnect());
|
|
864
|
+
}
|
|
865
|
+
handleMessage(message, _totalLength) {
|
|
866
|
+
const { cmd: cmdStr, data } = parseMessageBody(message);
|
|
867
|
+
switch (cmdStr) {
|
|
868
|
+
case "RREG": {
|
|
869
|
+
const regValue = senxorAckDecoder.RREG(data);
|
|
870
|
+
this.commandSender.resolveAck("RREG", regValue);
|
|
871
|
+
break;
|
|
872
|
+
}
|
|
873
|
+
case "WREG": {
|
|
874
|
+
const wregResult = senxorAckDecoder.WREG(data);
|
|
875
|
+
this.commandSender.resolveAck("WREG", wregResult);
|
|
876
|
+
break;
|
|
877
|
+
}
|
|
878
|
+
case "RRSE": {
|
|
879
|
+
const rrseResult = senxorAckDecoder.RRSE(data);
|
|
880
|
+
this.commandSender.resolveAck("RRSE", rrseResult);
|
|
881
|
+
break;
|
|
882
|
+
}
|
|
883
|
+
case "GFRA": {
|
|
884
|
+
const gfraData = senxorAckDecoder.GFRA(data);
|
|
885
|
+
this.handleGfraAck(gfraData);
|
|
886
|
+
break;
|
|
887
|
+
}
|
|
888
|
+
case "SERR":
|
|
889
|
+
this.handleSerrAck();
|
|
890
|
+
break;
|
|
891
|
+
default:
|
|
892
|
+
this.handleUnknownAck(cmdStr);
|
|
893
|
+
break;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
handlePortDisconnect() {
|
|
897
|
+
this.disconnectListener?.();
|
|
898
|
+
}
|
|
899
|
+
handlePortClose() {
|
|
900
|
+
this.closeListener?.();
|
|
901
|
+
}
|
|
902
|
+
handlePortOpen() {
|
|
903
|
+
this.openListener?.();
|
|
904
|
+
}
|
|
905
|
+
handleGfraAck(gfraData) {
|
|
906
|
+
const timestamp = Date.now();
|
|
907
|
+
const senxorRawData = {
|
|
908
|
+
header: gfraData.header,
|
|
909
|
+
frame: gfraData.frame,
|
|
910
|
+
timestamp
|
|
911
|
+
};
|
|
912
|
+
this.dataListener?.(senxorRawData);
|
|
913
|
+
}
|
|
914
|
+
handleParserError(error) {
|
|
915
|
+
const err = new SenxorTransportError("Parser error", error);
|
|
916
|
+
this.errorListener?.(err);
|
|
917
|
+
}
|
|
918
|
+
handlePortError(error) {
|
|
919
|
+
const err = new SenxorTransportError("Port I/O error", error);
|
|
920
|
+
this.errorListener?.(err);
|
|
921
|
+
}
|
|
922
|
+
handleSerrAck() {
|
|
923
|
+
const err = new SenxorTransportError("SERR Error: The device does not have a lens module installed");
|
|
924
|
+
this.errorListener?.(err);
|
|
925
|
+
}
|
|
926
|
+
handleUnknownAck(cmd) {
|
|
927
|
+
const err = new SenxorTransportError(`Unknown ACK: ${cmd}`);
|
|
928
|
+
this.errorListener?.(err);
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
//#endregion
|
|
932
|
+
export { CommandSender, SerialTransportBase, decodeUint8Array, isSenxorDevice, senxorPortOptions, toHexString };
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@senxor/serial-core",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.1.0",
|
|
5
|
+
"description": "Shared serial transport core for Senxor JavaScript SDK.",
|
|
6
|
+
"author": "Shui <zelongshui@meridianinno.com>",
|
|
7
|
+
"license": "Apache-2.0",
|
|
8
|
+
"homepage": "https://github.com/MeridianInnovation/senxor-js#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/MeridianInnovation/senxor-js.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/MeridianInnovation/senxor-js/issues"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./dist/index.js",
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^25.3.5",
|
|
26
|
+
"@typescript/native-preview": "7.0.0-dev.20260309.1",
|
|
27
|
+
"bumpp": "^10.4.1",
|
|
28
|
+
"tsdown": "^0.21.1",
|
|
29
|
+
"typescript": "^5.9.3",
|
|
30
|
+
"vitest": "^4.0.18"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"async-mutex": "^0.5.0",
|
|
34
|
+
"serial-packet-parser": "latest"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsdown",
|
|
38
|
+
"dev": "tsdown --watch",
|
|
39
|
+
"test": "vitest",
|
|
40
|
+
"typecheck": "tsc --noEmit"
|
|
41
|
+
}
|
|
42
|
+
}
|