@robdobsn/raftjs 1.1.1
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/.editorconfig +14 -0
- package/.gitattributes +11 -0
- package/.nvmrc +1 -0
- package/LICENSE +22 -0
- package/README.md +11 -0
- package/TODO.md +1 -0
- package/dist/RaftAttributeHandler.d.ts +12 -0
- package/dist/RaftAttributeHandler.js +241 -0
- package/dist/RaftAttributeHandler.js.map +1 -0
- package/dist/RaftChannel.d.ts +18 -0
- package/dist/RaftChannel.js +12 -0
- package/dist/RaftChannel.js.map +1 -0
- package/dist/RaftChannelWebBLE.d.ts +38 -0
- package/dist/RaftChannelWebBLE.js +274 -0
- package/dist/RaftChannelWebBLE.js.map +1 -0
- package/dist/RaftChannelWebSerial.d.ts +37 -0
- package/dist/RaftChannelWebSerial.js +319 -0
- package/dist/RaftChannelWebSerial.js.map +1 -0
- package/dist/RaftChannelWebSocket.d.ts +28 -0
- package/dist/RaftChannelWebSocket.js +197 -0
- package/dist/RaftChannelWebSocket.js.map +1 -0
- package/dist/RaftCommsStats.d.ts +39 -0
- package/dist/RaftCommsStats.js +128 -0
- package/dist/RaftCommsStats.js.map +1 -0
- package/dist/RaftConnEvents.d.ts +31 -0
- package/dist/RaftConnEvents.js +44 -0
- package/dist/RaftConnEvents.js.map +1 -0
- package/dist/RaftConnector.d.ts +242 -0
- package/dist/RaftConnector.js +613 -0
- package/dist/RaftConnector.js.map +1 -0
- package/dist/RaftCustomAttrHandler.d.ts +4 -0
- package/dist/RaftCustomAttrHandler.js +50 -0
- package/dist/RaftCustomAttrHandler.js.map +1 -0
- package/dist/RaftDeviceInfo.d.ts +64 -0
- package/dist/RaftDeviceInfo.js +36 -0
- package/dist/RaftDeviceInfo.js.map +1 -0
- package/dist/RaftDeviceManager.d.ts +37 -0
- package/dist/RaftDeviceManager.js +450 -0
- package/dist/RaftDeviceManager.js.map +1 -0
- package/dist/RaftDeviceMsg.d.ts +9 -0
- package/dist/RaftDeviceMsg.js +11 -0
- package/dist/RaftDeviceMsg.js.map +1 -0
- package/dist/RaftDeviceStates.d.ts +33 -0
- package/dist/RaftDeviceStates.js +60 -0
- package/dist/RaftDeviceStates.js.map +1 -0
- package/dist/RaftFileHandler.d.ts +52 -0
- package/dist/RaftFileHandler.js +502 -0
- package/dist/RaftFileHandler.js.map +1 -0
- package/dist/RaftLog.d.ts +22 -0
- package/dist/RaftLog.js +63 -0
- package/dist/RaftLog.js.map +1 -0
- package/dist/RaftMiniHDLC.d.ts +18 -0
- package/dist/RaftMiniHDLC.js +383 -0
- package/dist/RaftMiniHDLC.js.map +1 -0
- package/dist/RaftMsgHandler.d.ts +57 -0
- package/dist/RaftMsgHandler.js +480 -0
- package/dist/RaftMsgHandler.js.map +1 -0
- package/dist/RaftMsgTrackInfo.d.ts +17 -0
- package/dist/RaftMsgTrackInfo.js +42 -0
- package/dist/RaftMsgTrackInfo.js.map +1 -0
- package/dist/RaftProtocolDefs.d.ts +30 -0
- package/dist/RaftProtocolDefs.js +48 -0
- package/dist/RaftProtocolDefs.js.map +1 -0
- package/dist/RaftStreamHandler.d.ts +38 -0
- package/dist/RaftStreamHandler.js +257 -0
- package/dist/RaftStreamHandler.js.map +1 -0
- package/dist/RaftSystemType.d.ts +21 -0
- package/dist/RaftSystemType.js +3 -0
- package/dist/RaftSystemType.js.map +1 -0
- package/dist/RaftSystemUtils.d.ts +136 -0
- package/dist/RaftSystemUtils.js +410 -0
- package/dist/RaftSystemUtils.js.map +1 -0
- package/dist/RaftTypes.d.ts +184 -0
- package/dist/RaftTypes.js +157 -0
- package/dist/RaftTypes.js.map +1 -0
- package/dist/RaftUpdateEvents.d.ts +33 -0
- package/dist/RaftUpdateEvents.js +46 -0
- package/dist/RaftUpdateEvents.js.map +1 -0
- package/dist/RaftUpdateManager.d.ts +61 -0
- package/dist/RaftUpdateManager.js +618 -0
- package/dist/RaftUpdateManager.js.map +1 -0
- package/dist/RaftUtils.d.ts +125 -0
- package/dist/RaftUtils.js +454 -0
- package/dist/RaftUtils.js.map +1 -0
- package/dist/RaftWifiTypes.d.ts +23 -0
- package/dist/RaftWifiTypes.js +43 -0
- package/dist/RaftWifiTypes.js.map +1 -0
- package/dist/TestDataGen.d.ts +7 -0
- package/dist/TestDataGen.js +133 -0
- package/dist/TestDataGen.js.map +1 -0
- package/dist/main.d.ts +18 -0
- package/dist/main.js +42 -0
- package/dist/main.js.map +1 -0
- package/eslint.config.mjs +33 -0
- package/examples/dashboard/package.json +39 -0
- package/examples/dashboard/src/ConnManager.ts +86 -0
- package/examples/dashboard/src/Main.tsx +100 -0
- package/examples/dashboard/src/StatusScreen.tsx +72 -0
- package/examples/dashboard/src/SystemTypeCog/CogStateInfo.ts +144 -0
- package/examples/dashboard/src/SystemTypeCog/SystemTypeCog.ts +77 -0
- package/examples/dashboard/src/SystemTypeMarty/RICAddOn.ts +70 -0
- package/examples/dashboard/src/SystemTypeMarty/RICAddOnBase.ts +33 -0
- package/examples/dashboard/src/SystemTypeMarty/RICAddOnManager.ts +342 -0
- package/examples/dashboard/src/SystemTypeMarty/RICCommsStats.ts +170 -0
- package/examples/dashboard/src/SystemTypeMarty/RICHWElem.ts +123 -0
- package/examples/dashboard/src/SystemTypeMarty/RICLEDPatternChecker.ts +207 -0
- package/examples/dashboard/src/SystemTypeMarty/RICROSSerial.ts +464 -0
- package/examples/dashboard/src/SystemTypeMarty/RICServoFaultDetector.ts +146 -0
- package/examples/dashboard/src/SystemTypeMarty/RICStateInfo.ts +32 -0
- package/examples/dashboard/src/SystemTypeMarty/RICSystemUtils.ts +371 -0
- package/examples/dashboard/src/SystemTypeMarty/RICTypes.ts +20 -0
- package/examples/dashboard/src/SystemTypeMarty/SystemTypeMarty.ts +113 -0
- package/examples/dashboard/src/index.html +15 -0
- package/examples/dashboard/src/index.tsx +15 -0
- package/examples/dashboard/src/styles.css +122 -0
- package/examples/dashboard/tsconfig.json +18 -0
- package/jest.config.js +11 -0
- package/package.json +50 -0
- package/src/RaftAttributeHandler.ts +289 -0
- package/src/RaftChannel.ts +30 -0
- package/src/RaftChannelWebBLE.ts +342 -0
- package/src/RaftChannelWebSerial.ts +408 -0
- package/src/RaftChannelWebSocket.ts +245 -0
- package/src/RaftCommsStats.ts +142 -0
- package/src/RaftConnEvents.ts +46 -0
- package/src/RaftConnector.ts +745 -0
- package/src/RaftCustomAttrHandler.ts +54 -0
- package/src/RaftDeviceInfo.ts +104 -0
- package/src/RaftDeviceManager.ts +542 -0
- package/src/RaftDeviceMsg.ts +20 -0
- package/src/RaftDeviceStates.ts +89 -0
- package/src/RaftFileHandler.ts +668 -0
- package/src/RaftLog.ts +70 -0
- package/src/RaftMiniHDLC.ts +396 -0
- package/src/RaftMsgHandler.ts +778 -0
- package/src/RaftMsgTrackInfo.ts +51 -0
- package/src/RaftProtocolDefs.ts +46 -0
- package/src/RaftStreamHandler.ts +328 -0
- package/src/RaftSystemType.ts +25 -0
- package/src/RaftSystemUtils.ts +487 -0
- package/src/RaftTypes.ts +250 -0
- package/src/RaftUpdateEvents.ts +48 -0
- package/src/RaftUpdateManager.ts +778 -0
- package/src/RaftUtils.ts +484 -0
- package/src/RaftWifiTypes.ts +36 -0
- package/src/TestDataGen.ts +157 -0
- package/src/main.ts +28 -0
- package/testdata/TestDeviceTypeRecs.json +492 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
//
|
|
3
|
+
// RaftChannelWebSerial
|
|
4
|
+
// Part of RaftJS
|
|
5
|
+
//
|
|
6
|
+
// Rob Dobson & Chris Greening 2020-2024
|
|
7
|
+
// (C) 2020-2024 All rights reserved
|
|
8
|
+
//
|
|
9
|
+
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
10
|
+
|
|
11
|
+
import RaftChannel from "./RaftChannel";
|
|
12
|
+
import RaftMsgHandler from "./RaftMsgHandler";
|
|
13
|
+
import RaftLog from "./RaftLog";
|
|
14
|
+
import { RaftConnEvent, RaftConnEventFn } from "./RaftConnEvents";
|
|
15
|
+
import { ConnectorOptions } from "./RaftSystemType";
|
|
16
|
+
|
|
17
|
+
type TWebParityType = 'none' | 'even' | 'odd';
|
|
18
|
+
type TWebFlowControlType = 'none' | 'hardware';
|
|
19
|
+
|
|
20
|
+
interface TWebSerialOptions {
|
|
21
|
+
baudRate: number;
|
|
22
|
+
dataBits?: number | undefined;
|
|
23
|
+
stopBits?: number | undefined;
|
|
24
|
+
parity?: TWebParityType | undefined;
|
|
25
|
+
bufferSize?: number | undefined;
|
|
26
|
+
flowControl?: TWebFlowControlType | undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare class TWebSerialPort extends EventTarget {
|
|
30
|
+
readonly readable: ReadableStream<Uint8Array> | null;
|
|
31
|
+
readonly writable: WritableStream<Uint8Array> | null;
|
|
32
|
+
|
|
33
|
+
open(options: TWebSerialOptions): Promise<void>;
|
|
34
|
+
close(): Promise<void>;
|
|
35
|
+
forget(): Promise<void>;
|
|
36
|
+
|
|
37
|
+
addEventListener(
|
|
38
|
+
type: 'connect' | 'disconnect',
|
|
39
|
+
listener: (this: this, ev: Event) => any,
|
|
40
|
+
useCapture?: boolean): void;
|
|
41
|
+
addEventListener(
|
|
42
|
+
type: string,
|
|
43
|
+
listener: EventListenerOrEventListenerObject | null,
|
|
44
|
+
options?: boolean | AddEventListenerOptions): void;
|
|
45
|
+
removeEventListener(
|
|
46
|
+
type: 'connect' | 'disconnect',
|
|
47
|
+
callback: (this: this, ev: Event) => any,
|
|
48
|
+
useCapture?: boolean): void;
|
|
49
|
+
removeEventListener(
|
|
50
|
+
type: string,
|
|
51
|
+
callback: EventListenerOrEventListenerObject | null,
|
|
52
|
+
options?: EventListenerOptions | boolean): void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface TWebSerialPortFilter {
|
|
56
|
+
usbVendorId?: number | undefined;
|
|
57
|
+
usbProductId?: number | undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface TWebSerialPortRequestOptions {
|
|
61
|
+
filters?: TWebSerialPortFilter[] | undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
declare class TWebSerial extends EventTarget {
|
|
65
|
+
requestPort(options?: TWebSerialPortRequestOptions): Promise<TWebSerialPort>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface NavigatorWithSerial {
|
|
69
|
+
readonly serial: TWebSerial;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default class RaftChannelWebSerial implements RaftChannel {
|
|
73
|
+
|
|
74
|
+
// Message handler
|
|
75
|
+
private _raftMsgHandler: RaftMsgHandler | null = null;
|
|
76
|
+
|
|
77
|
+
private _port: TWebSerialPort | null = null;
|
|
78
|
+
private _reader?: ReadableStreamDefaultReader<Uint8Array>;
|
|
79
|
+
|
|
80
|
+
// Last message tx time
|
|
81
|
+
// private _msgTxTimeLast = Date.now();
|
|
82
|
+
// private _msgTxMinTimeBetweenMs = 15;
|
|
83
|
+
|
|
84
|
+
// Is connected
|
|
85
|
+
private _isConnected = false;
|
|
86
|
+
private _connPaused = false;
|
|
87
|
+
|
|
88
|
+
private _serialBuffer: number[] = [];
|
|
89
|
+
|
|
90
|
+
private _escapeSeqCode = 0;
|
|
91
|
+
private _OVERASCII_ESCAPE_1 = 0x85;
|
|
92
|
+
private _OVERASCII_ESCAPE_2 = 0x8E;
|
|
93
|
+
private _OVERASCII_ESCAPE_3 = 0x8F;
|
|
94
|
+
private _OVERASCII_MOD_CODE = 0x20;
|
|
95
|
+
|
|
96
|
+
// Conn event fn
|
|
97
|
+
private _onConnEvent: RaftConnEventFn | null = null;
|
|
98
|
+
|
|
99
|
+
// File Handler parameters
|
|
100
|
+
private _requestedBatchAckSize = 1;
|
|
101
|
+
private _requestedFileBlockSize = 1200;
|
|
102
|
+
|
|
103
|
+
fhBatchAckSize(): number { return this._requestedBatchAckSize; }
|
|
104
|
+
fhFileBlockSize(): number { return this._requestedFileBlockSize; }
|
|
105
|
+
|
|
106
|
+
// isConnected
|
|
107
|
+
isConnected(): boolean {
|
|
108
|
+
return this._isConnected;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Set message handler
|
|
112
|
+
setMsgHandler(raftMsgHandler: RaftMsgHandler): void {
|
|
113
|
+
this._raftMsgHandler = raftMsgHandler;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Serial interface will require subscription, but don't start it by default
|
|
117
|
+
requiresSubscription(): boolean {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// RICREST command before disconnect
|
|
122
|
+
ricRestCmdBeforeDisconnect(): string | null {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Set onConnEvent handler
|
|
127
|
+
setOnConnEvent(connEventFn: RaftConnEventFn): void {
|
|
128
|
+
this._onConnEvent = connEventFn;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Get connected locator
|
|
132
|
+
getConnectedLocator(): string | object {
|
|
133
|
+
return this._port || "";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Connect to a device
|
|
137
|
+
async connect(locator: string | object, _connectorOptions: ConnectorOptions): Promise<boolean> {
|
|
138
|
+
console.log("_connectorOptions", _connectorOptions);
|
|
139
|
+
// Debug
|
|
140
|
+
RaftLog.debug("RaftChannelWebSerial.connect " + locator.toString());
|
|
141
|
+
|
|
142
|
+
// Check already connected
|
|
143
|
+
if (await this.isConnected()) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
if (('serial' in navigator) && (!this._port || locator != "reusePort")) {
|
|
149
|
+
const port = await (navigator as NavigatorWithSerial).serial.requestPort();
|
|
150
|
+
this._port = port;
|
|
151
|
+
}
|
|
152
|
+
// Connect
|
|
153
|
+
if (!this._port)
|
|
154
|
+
return false;
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
RaftLog.info("opening port");
|
|
158
|
+
await this._port.open({ baudRate: 115200 });
|
|
159
|
+
} catch (err: any) {
|
|
160
|
+
if (err.name == "InvalidStateError") {
|
|
161
|
+
RaftLog.debug(`Opening port failed - already open ${err}`);
|
|
162
|
+
} else {
|
|
163
|
+
RaftLog.error(`Opening port failed: ${err}`);
|
|
164
|
+
throw err;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
this._isConnected = true;
|
|
169
|
+
|
|
170
|
+
// start read loop
|
|
171
|
+
this._readLoop();
|
|
172
|
+
|
|
173
|
+
this._port.addEventListener('disconnect', (event) => {
|
|
174
|
+
RaftLog.debug("WebSerial disconnect " + JSON.stringify(event));
|
|
175
|
+
if (this._onConnEvent)
|
|
176
|
+
this._onConnEvent(RaftConnEvent.CONN_DISCONNECTED);
|
|
177
|
+
});
|
|
178
|
+
// TODO: handle errors
|
|
179
|
+
} catch (err) {
|
|
180
|
+
RaftLog.error("RaftChannelWebSerial.connect fail. Error: " + JSON.stringify(err));
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Disconnect
|
|
188
|
+
async disconnect(): Promise<void> {
|
|
189
|
+
|
|
190
|
+
// Not connected
|
|
191
|
+
this._isConnected = false;
|
|
192
|
+
|
|
193
|
+
RaftLog.debug(`RaftChannelWebSerial.disconnect attempting to close webserial`);
|
|
194
|
+
|
|
195
|
+
while (this._reader) {
|
|
196
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Disconnect webserial
|
|
200
|
+
try {
|
|
201
|
+
if (this._port)
|
|
202
|
+
await this._port.close();
|
|
203
|
+
} catch (err) {
|
|
204
|
+
console.debug(`Error closing port ${err}`);
|
|
205
|
+
}
|
|
206
|
+
if (this._onConnEvent)
|
|
207
|
+
this._onConnEvent(RaftConnEvent.CONN_DISCONNECTED);
|
|
208
|
+
|
|
209
|
+
RaftLog.debug("WebSerial port closed");
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
pauseConnection(pause: boolean): void {
|
|
214
|
+
this._connPaused = pause;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private _overasciiDecodeByte(ch: number) {
|
|
218
|
+
// Check if in escape sequence
|
|
219
|
+
if (this._escapeSeqCode != 0) {
|
|
220
|
+
const prevEscCode = this._escapeSeqCode;
|
|
221
|
+
this._escapeSeqCode = 0;
|
|
222
|
+
if (prevEscCode == 1) {
|
|
223
|
+
return (ch ^ this._OVERASCII_MOD_CODE) & 0x7f;
|
|
224
|
+
} else if (prevEscCode == 2) {
|
|
225
|
+
return ch ^ this._OVERASCII_MOD_CODE;
|
|
226
|
+
}
|
|
227
|
+
return ch;
|
|
228
|
+
} else if (ch == this._OVERASCII_ESCAPE_1) {
|
|
229
|
+
this._escapeSeqCode = 1;
|
|
230
|
+
return -1;
|
|
231
|
+
} else if (ch == this._OVERASCII_ESCAPE_2) {
|
|
232
|
+
this._escapeSeqCode = 2;
|
|
233
|
+
return -1;
|
|
234
|
+
} else if (ch == this._OVERASCII_ESCAPE_3) {
|
|
235
|
+
this._escapeSeqCode = 3;
|
|
236
|
+
return -1;
|
|
237
|
+
} else {
|
|
238
|
+
return ch & 0x7f;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private _overasciiEncode(inData: Uint8Array) {
|
|
243
|
+
/*
|
|
244
|
+
Encode frame
|
|
245
|
+
Values 0x00-0x0F map to ESCAPE_CODE_1, VALUE_XOR_20H_AND_MSB_SET
|
|
246
|
+
Values 0x10-0x7f map to VALUE_WITH_MSB_SET
|
|
247
|
+
Values 0x80-0x8F map to ESCAPE_CODE_2, VALUE_XOR_20H
|
|
248
|
+
Values 0x90-0xff map to ESCAPE_CODE_3, VALUE
|
|
249
|
+
Value ESCAPE_CODE_1 maps to ESCAPE_CODE_1 + VALUE
|
|
250
|
+
Args:
|
|
251
|
+
inData: data to encode (bytes)
|
|
252
|
+
Returns:
|
|
253
|
+
encoded frame (bytes)
|
|
254
|
+
*/
|
|
255
|
+
// Iterate over frame
|
|
256
|
+
const encodedFrame: number[] = [];
|
|
257
|
+
for (let i = 0; i < inData.length; i++) {
|
|
258
|
+
if (inData[i] <= 0x0f) {
|
|
259
|
+
encodedFrame.push(this._OVERASCII_ESCAPE_1);
|
|
260
|
+
encodedFrame.push((inData[i] ^ this._OVERASCII_MOD_CODE) | 0x80);
|
|
261
|
+
} else if ((inData[i] >= 0x10) && (inData[i] <= 0x7f)) {
|
|
262
|
+
encodedFrame.push(inData[i] | 0x80);
|
|
263
|
+
} else if ((inData[i] >= 0x80) && (inData[i] <= 0x8f)) {
|
|
264
|
+
encodedFrame.push(this._OVERASCII_ESCAPE_2);
|
|
265
|
+
encodedFrame.push(inData[i] ^ this._OVERASCII_MOD_CODE);
|
|
266
|
+
} else {
|
|
267
|
+
encodedFrame.push(this._OVERASCII_ESCAPE_3);
|
|
268
|
+
encodedFrame.push(inData[i]);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return new Uint8Array(encodedFrame);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
// Handle notifications
|
|
276
|
+
_onMsgRx(msg: Uint8Array | null): void {
|
|
277
|
+
|
|
278
|
+
if (msg === null) return;
|
|
279
|
+
// Debug
|
|
280
|
+
//const decoder = new TextDecoder();
|
|
281
|
+
//RaftLog.debug(`RaftChannelWebSerial._onMsgRx ${decoder.decode(msg)}`);
|
|
282
|
+
|
|
283
|
+
const overasciiBuffer: number[] = [];
|
|
284
|
+
for (let i = 0; i < msg.length; i++) {
|
|
285
|
+
if (msg[i] > 127) {
|
|
286
|
+
const ch = this._overasciiDecodeByte(msg[i]);
|
|
287
|
+
if (ch != -1) {
|
|
288
|
+
overasciiBuffer.push(ch);
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
this._serialBuffer.push(msg[i]);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (this._raftMsgHandler)
|
|
295
|
+
this._raftMsgHandler.handleNewRxMsg(new Uint8Array(overasciiBuffer));
|
|
296
|
+
|
|
297
|
+
// any output over the non overascii channel will be delimited by a new line character
|
|
298
|
+
// scan for this, and log any lines as they occur before removing them from the buffer
|
|
299
|
+
if (this._serialBuffer.includes(0x0a)) {
|
|
300
|
+
const decoder = new TextDecoder();
|
|
301
|
+
RaftLog.debug(decoder.decode(new Uint8Array(this._serialBuffer.slice(0, this._serialBuffer.indexOf(0x0a)))));
|
|
302
|
+
this._serialBuffer.splice(0, this._serialBuffer.indexOf(0x0a) + 1);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Send a message
|
|
307
|
+
async sendTxMsg(
|
|
308
|
+
msg: Uint8Array,
|
|
309
|
+
sendWithResponse: boolean
|
|
310
|
+
): Promise<boolean> {
|
|
311
|
+
|
|
312
|
+
// Check connected
|
|
313
|
+
if (!this._isConnected || !this._port)
|
|
314
|
+
return false;
|
|
315
|
+
|
|
316
|
+
// Debug
|
|
317
|
+
const decoder = new TextDecoder();
|
|
318
|
+
RaftLog.verbose(`RaftChannelWebSerial.sendTxMsg ${msg.toString()} ${decoder.decode(msg)} sendWithResp ${sendWithResponse.toString()}`);
|
|
319
|
+
|
|
320
|
+
try {
|
|
321
|
+
if (this._port.writable != null) {
|
|
322
|
+
const writer = this._port.writable.getWriter();
|
|
323
|
+
await writer.write(this._overasciiEncode(msg));
|
|
324
|
+
writer.releaseLock();
|
|
325
|
+
}
|
|
326
|
+
} catch (err) {
|
|
327
|
+
RaftLog.warn("sendMsg error: " + JSON.stringify(err));
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async sendTxMsgNoAwait(
|
|
334
|
+
msg: Uint8Array,
|
|
335
|
+
sendWithResponse: boolean
|
|
336
|
+
): Promise<boolean> {
|
|
337
|
+
|
|
338
|
+
// Check connected
|
|
339
|
+
if (!this._isConnected || !this._port)
|
|
340
|
+
return false;
|
|
341
|
+
|
|
342
|
+
// Debug
|
|
343
|
+
RaftLog.verbose(`RaftChannelWebSerial.sendTxMsgNoAwait ${msg.toString()} sendWithResp ${sendWithResponse.toString()}`);
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
if (this._port.writable != null) {
|
|
347
|
+
const writer = this._port.writable.getWriter();
|
|
348
|
+
writer.write(msg).then(() => { writer.releaseLock(); });
|
|
349
|
+
}
|
|
350
|
+
} catch (err) {
|
|
351
|
+
RaftLog.error("sendMsg error: " + JSON.stringify(err));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async _readLoop() {
|
|
358
|
+
RaftLog.debug("Starting read loop");
|
|
359
|
+
|
|
360
|
+
if (!this._port)
|
|
361
|
+
return;
|
|
362
|
+
|
|
363
|
+
let retries = 10;
|
|
364
|
+
try {
|
|
365
|
+
if (!this._port.readable) {
|
|
366
|
+
RaftLog.error("RaftChannelWebSerial _readLoop port is not readble");
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
this._reader = this._port.readable.getReader();
|
|
370
|
+
while (this._port.readable && this._isConnected) {
|
|
371
|
+
if (this._connPaused) {
|
|
372
|
+
if (this._reader) {
|
|
373
|
+
this._reader.releaseLock();
|
|
374
|
+
this._reader = undefined;
|
|
375
|
+
}
|
|
376
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
try {
|
|
380
|
+
if (!this._reader) this._reader = this._port.readable.getReader();
|
|
381
|
+
const { value, done } = await this._reader.read();
|
|
382
|
+
if (done) {
|
|
383
|
+
this._reader.releaseLock();
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
if (!value || value.length === 0) {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
this._onMsgRx(new Uint8Array(value));
|
|
391
|
+
} catch (err) {
|
|
392
|
+
RaftLog.error("read loop issue: " + JSON.stringify(err));
|
|
393
|
+
retries -= 1;
|
|
394
|
+
if (!retries) break;
|
|
395
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
396
|
+
this._reader = this._port.readable.getReader();
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (this._reader) this._reader.releaseLock();
|
|
400
|
+
this._reader = undefined;
|
|
401
|
+
} catch (err) {
|
|
402
|
+
RaftLog.error("Read loop got disconnected. err: " + JSON.stringify(err));
|
|
403
|
+
}
|
|
404
|
+
// Disconnected!
|
|
405
|
+
this._isConnected = false;
|
|
406
|
+
RaftLog.debug("Finished read loop");
|
|
407
|
+
}
|
|
408
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
//
|
|
3
|
+
// RaftChannelWebSockets
|
|
4
|
+
// Part of RaftJS
|
|
5
|
+
//
|
|
6
|
+
// Rob Dobson & Chris Greening 2020-2024
|
|
7
|
+
// (C) 2020-2024 All rights reserved
|
|
8
|
+
//
|
|
9
|
+
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
10
|
+
|
|
11
|
+
import RaftChannel from "./RaftChannel";
|
|
12
|
+
import WebSocket from "isomorphic-ws";
|
|
13
|
+
import RaftMsgHandler from "./RaftMsgHandler";
|
|
14
|
+
import RaftLog from "./RaftLog";
|
|
15
|
+
import RaftUtils from "./RaftUtils";
|
|
16
|
+
import { RaftConnEvent, RaftConnEventFn } from "./RaftConnEvents";
|
|
17
|
+
import { ConnectorOptions } from "./RaftSystemType";
|
|
18
|
+
|
|
19
|
+
export default class RaftChannelWebSocket implements RaftChannel {
|
|
20
|
+
|
|
21
|
+
// Message handler
|
|
22
|
+
private _raftMsgHandler: RaftMsgHandler | null = null;
|
|
23
|
+
|
|
24
|
+
// Websocket we are connected to
|
|
25
|
+
private _webSocket: WebSocket | null = null;
|
|
26
|
+
|
|
27
|
+
// Last message tx time
|
|
28
|
+
// private _msgTxTimeLast = Date.now();
|
|
29
|
+
// private _msgTxMinTimeBetweenMs = 15;
|
|
30
|
+
|
|
31
|
+
// Is connected
|
|
32
|
+
private _isConnected = false;
|
|
33
|
+
|
|
34
|
+
// Conn event fn
|
|
35
|
+
private _onConnEvent: RaftConnEventFn | null = null;
|
|
36
|
+
|
|
37
|
+
// File Handler parameters
|
|
38
|
+
private _requestedBatchAckSize = 10;
|
|
39
|
+
private _requestedFileBlockSize = 500;
|
|
40
|
+
|
|
41
|
+
fhBatchAckSize(): number { return this._requestedBatchAckSize; }
|
|
42
|
+
fhFileBlockSize(): number { return this._requestedFileBlockSize; }
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
// isConnected
|
|
46
|
+
isConnected(): boolean {
|
|
47
|
+
return this._isConnected;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Set message handler
|
|
51
|
+
setMsgHandler(raftMsgHandler: RaftMsgHandler): void {
|
|
52
|
+
this._raftMsgHandler = raftMsgHandler;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// WebSocket interfaces require subscription to published messages
|
|
56
|
+
requiresSubscription(): boolean {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// RICREST command before disconnect
|
|
61
|
+
ricRestCmdBeforeDisconnect(): string | null {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Set onConnEvent handler
|
|
66
|
+
setOnConnEvent(connEventFn: RaftConnEventFn): void {
|
|
67
|
+
this._onConnEvent = connEventFn;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Get connected locator
|
|
71
|
+
getConnectedLocator(): string | object {
|
|
72
|
+
return this._webSocket;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Connect to a device
|
|
76
|
+
async connect(locator: string | object, connectorOptions: ConnectorOptions): Promise<boolean> {
|
|
77
|
+
|
|
78
|
+
// Debug
|
|
79
|
+
RaftLog.debug("RaftChannelWebSocket.connect " + locator.toString());
|
|
80
|
+
|
|
81
|
+
// Get ws suffix
|
|
82
|
+
const wsSuffix = connectorOptions ? (connectorOptions.wsSuffix ? connectorOptions.wsSuffix : "ws") : "ws";
|
|
83
|
+
|
|
84
|
+
// Connect
|
|
85
|
+
const connOk = await this._wsConnect("ws://" + locator + "/" + wsSuffix);
|
|
86
|
+
return connOk;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Disconnect
|
|
90
|
+
async disconnect(): Promise<void> {
|
|
91
|
+
|
|
92
|
+
// Not connected
|
|
93
|
+
this._isConnected = false;
|
|
94
|
+
|
|
95
|
+
// Disconnect websocket
|
|
96
|
+
this._webSocket?.close(1000);
|
|
97
|
+
|
|
98
|
+
// Debug
|
|
99
|
+
RaftLog.debug(`RaftChannelWebSocket.disconnect attempting to close websocket`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
pauseConnection(pause: boolean): void { RaftLog.verbose(`pauseConnection ${pause} - no effect for this channel type`); return; }
|
|
103
|
+
|
|
104
|
+
// Handle notifications
|
|
105
|
+
_onMsgRx(msg: Uint8Array | null): void {
|
|
106
|
+
|
|
107
|
+
// Debug
|
|
108
|
+
if (msg !== null) {
|
|
109
|
+
RaftLog.verbose(`RaftChannelWebSocket._onMsgRx ${RaftUtils.bufferToHex(msg)}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Handle message
|
|
113
|
+
if (msg !== null && this._raftMsgHandler) {
|
|
114
|
+
this._raftMsgHandler.handleNewRxMsg(msg);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Send a message
|
|
120
|
+
async sendTxMsg(
|
|
121
|
+
msg: Uint8Array,
|
|
122
|
+
sendWithResponse: boolean
|
|
123
|
+
): Promise<boolean> {
|
|
124
|
+
|
|
125
|
+
// Check connected
|
|
126
|
+
if (!this._isConnected)
|
|
127
|
+
return false;
|
|
128
|
+
|
|
129
|
+
// Debug
|
|
130
|
+
RaftLog.verbose(`RaftChannelWebSocket.sendTxMsg ${msg.toString()} sendWithResp ${sendWithResponse.toString()}`);
|
|
131
|
+
|
|
132
|
+
// Send over websocket
|
|
133
|
+
try {
|
|
134
|
+
await this._webSocket?.send(msg);
|
|
135
|
+
} catch (error: unknown) {
|
|
136
|
+
RaftLog.warn(`RaftChannelWebSocket.sendTxMsg - send failed ${error}`);
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async sendTxMsgNoAwait(
|
|
143
|
+
msg: Uint8Array,
|
|
144
|
+
sendWithResponse: boolean
|
|
145
|
+
): Promise<boolean> {
|
|
146
|
+
|
|
147
|
+
// Check connected
|
|
148
|
+
if (!this._isConnected)
|
|
149
|
+
return false;
|
|
150
|
+
|
|
151
|
+
// Debug
|
|
152
|
+
RaftLog.verbose(`RaftChannelWebSocket.sendTxMsgNoAwait ${msg.toString()} sendWithResp ${sendWithResponse.toString()}`);
|
|
153
|
+
|
|
154
|
+
// Send over websocket
|
|
155
|
+
this._webSocket?.send(msg);
|
|
156
|
+
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async _wsConnect(locator: string | object): Promise<boolean> {
|
|
161
|
+
|
|
162
|
+
// Check already connected
|
|
163
|
+
if (await this.isConnected()) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Form websocket address
|
|
168
|
+
const wsURL = locator.toString();
|
|
169
|
+
|
|
170
|
+
// Connect to websocket
|
|
171
|
+
// try {
|
|
172
|
+
// this._webSocket = await this._webSocketOpen(wsURL);
|
|
173
|
+
// } catch (error: any) {
|
|
174
|
+
// RaftLog.debug(`Unable to create WebSocket ${error.toString()}`);
|
|
175
|
+
// return false;
|
|
176
|
+
// }
|
|
177
|
+
this._webSocket = null;
|
|
178
|
+
return new Promise((resolve: (value: boolean | PromiseLike<boolean>) => void,
|
|
179
|
+
reject: (reason?: unknown) => void) => {
|
|
180
|
+
this._webSocketOpen(wsURL).then((ws) => {
|
|
181
|
+
this._webSocket = ws;
|
|
182
|
+
RaftLog.debug(`_wsConnect - opened connection`);
|
|
183
|
+
|
|
184
|
+
// Handle messages
|
|
185
|
+
this._webSocket.onmessage = (evt: WebSocket.MessageEvent) => {
|
|
186
|
+
// RaftLog.debug("WebSocket rx");
|
|
187
|
+
if (evt.data instanceof ArrayBuffer) {
|
|
188
|
+
const msg = new Uint8Array(evt.data);
|
|
189
|
+
this._onMsgRx(msg);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Handle close event
|
|
194
|
+
this._webSocket.onclose = (evt: WebSocket.CloseEvent) => {
|
|
195
|
+
RaftLog.info(`_wsConnect - closed code ${evt.code} wasClean ${evt.wasClean} reason ${evt.reason}`);
|
|
196
|
+
this._webSocket = null;
|
|
197
|
+
this._isConnected = false;
|
|
198
|
+
|
|
199
|
+
// Event handler
|
|
200
|
+
if (this._onConnEvent) {
|
|
201
|
+
this._onConnEvent(RaftConnEvent.CONN_DISCONNECTED);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Resolve the promise - success
|
|
206
|
+
resolve(true);
|
|
207
|
+
}).catch((err: unknown) => {
|
|
208
|
+
if (err instanceof Error) {
|
|
209
|
+
RaftLog.verbose(`WS open failed ${err.toString()}`)
|
|
210
|
+
}
|
|
211
|
+
// Resolve - failed
|
|
212
|
+
reject(false);
|
|
213
|
+
})
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private async _webSocketOpen(url: string): Promise<WebSocket> {
|
|
218
|
+
return new Promise((resolve, reject) => {
|
|
219
|
+
|
|
220
|
+
// Debug
|
|
221
|
+
// RaftLog.debug('Attempting WebSocket connection');
|
|
222
|
+
|
|
223
|
+
// Open the socket
|
|
224
|
+
try {
|
|
225
|
+
const webSocket = new WebSocket(url);
|
|
226
|
+
|
|
227
|
+
// Open socket
|
|
228
|
+
webSocket.binaryType = "arraybuffer";
|
|
229
|
+
webSocket.onopen = (_evt: WebSocket.Event) => {
|
|
230
|
+
RaftLog.debug(`RaftChannelWebSocket._webSocketOpen - onopen ${_evt.toString()}`);
|
|
231
|
+
// // We're connected
|
|
232
|
+
this._isConnected = true;
|
|
233
|
+
resolve(webSocket);
|
|
234
|
+
};
|
|
235
|
+
webSocket.onerror = function (evt: WebSocket.ErrorEvent) {
|
|
236
|
+
RaftLog.warn(`RaftChannelWebSocket._webSocketOpen - onerror: ${evt.message}`);
|
|
237
|
+
reject(evt);
|
|
238
|
+
}
|
|
239
|
+
} catch (error: unknown) {
|
|
240
|
+
RaftLog.warn(`RaftChannelWebSocket._webSocketOpen - open failed ${error}`);
|
|
241
|
+
reject(error);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|