@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.
Files changed (149) hide show
  1. package/.editorconfig +14 -0
  2. package/.gitattributes +11 -0
  3. package/.nvmrc +1 -0
  4. package/LICENSE +22 -0
  5. package/README.md +11 -0
  6. package/TODO.md +1 -0
  7. package/dist/RaftAttributeHandler.d.ts +12 -0
  8. package/dist/RaftAttributeHandler.js +241 -0
  9. package/dist/RaftAttributeHandler.js.map +1 -0
  10. package/dist/RaftChannel.d.ts +18 -0
  11. package/dist/RaftChannel.js +12 -0
  12. package/dist/RaftChannel.js.map +1 -0
  13. package/dist/RaftChannelWebBLE.d.ts +38 -0
  14. package/dist/RaftChannelWebBLE.js +274 -0
  15. package/dist/RaftChannelWebBLE.js.map +1 -0
  16. package/dist/RaftChannelWebSerial.d.ts +37 -0
  17. package/dist/RaftChannelWebSerial.js +319 -0
  18. package/dist/RaftChannelWebSerial.js.map +1 -0
  19. package/dist/RaftChannelWebSocket.d.ts +28 -0
  20. package/dist/RaftChannelWebSocket.js +197 -0
  21. package/dist/RaftChannelWebSocket.js.map +1 -0
  22. package/dist/RaftCommsStats.d.ts +39 -0
  23. package/dist/RaftCommsStats.js +128 -0
  24. package/dist/RaftCommsStats.js.map +1 -0
  25. package/dist/RaftConnEvents.d.ts +31 -0
  26. package/dist/RaftConnEvents.js +44 -0
  27. package/dist/RaftConnEvents.js.map +1 -0
  28. package/dist/RaftConnector.d.ts +242 -0
  29. package/dist/RaftConnector.js +613 -0
  30. package/dist/RaftConnector.js.map +1 -0
  31. package/dist/RaftCustomAttrHandler.d.ts +4 -0
  32. package/dist/RaftCustomAttrHandler.js +50 -0
  33. package/dist/RaftCustomAttrHandler.js.map +1 -0
  34. package/dist/RaftDeviceInfo.d.ts +64 -0
  35. package/dist/RaftDeviceInfo.js +36 -0
  36. package/dist/RaftDeviceInfo.js.map +1 -0
  37. package/dist/RaftDeviceManager.d.ts +37 -0
  38. package/dist/RaftDeviceManager.js +450 -0
  39. package/dist/RaftDeviceManager.js.map +1 -0
  40. package/dist/RaftDeviceMsg.d.ts +9 -0
  41. package/dist/RaftDeviceMsg.js +11 -0
  42. package/dist/RaftDeviceMsg.js.map +1 -0
  43. package/dist/RaftDeviceStates.d.ts +33 -0
  44. package/dist/RaftDeviceStates.js +60 -0
  45. package/dist/RaftDeviceStates.js.map +1 -0
  46. package/dist/RaftFileHandler.d.ts +52 -0
  47. package/dist/RaftFileHandler.js +502 -0
  48. package/dist/RaftFileHandler.js.map +1 -0
  49. package/dist/RaftLog.d.ts +22 -0
  50. package/dist/RaftLog.js +63 -0
  51. package/dist/RaftLog.js.map +1 -0
  52. package/dist/RaftMiniHDLC.d.ts +18 -0
  53. package/dist/RaftMiniHDLC.js +383 -0
  54. package/dist/RaftMiniHDLC.js.map +1 -0
  55. package/dist/RaftMsgHandler.d.ts +57 -0
  56. package/dist/RaftMsgHandler.js +480 -0
  57. package/dist/RaftMsgHandler.js.map +1 -0
  58. package/dist/RaftMsgTrackInfo.d.ts +17 -0
  59. package/dist/RaftMsgTrackInfo.js +42 -0
  60. package/dist/RaftMsgTrackInfo.js.map +1 -0
  61. package/dist/RaftProtocolDefs.d.ts +30 -0
  62. package/dist/RaftProtocolDefs.js +48 -0
  63. package/dist/RaftProtocolDefs.js.map +1 -0
  64. package/dist/RaftStreamHandler.d.ts +38 -0
  65. package/dist/RaftStreamHandler.js +257 -0
  66. package/dist/RaftStreamHandler.js.map +1 -0
  67. package/dist/RaftSystemType.d.ts +21 -0
  68. package/dist/RaftSystemType.js +3 -0
  69. package/dist/RaftSystemType.js.map +1 -0
  70. package/dist/RaftSystemUtils.d.ts +136 -0
  71. package/dist/RaftSystemUtils.js +410 -0
  72. package/dist/RaftSystemUtils.js.map +1 -0
  73. package/dist/RaftTypes.d.ts +184 -0
  74. package/dist/RaftTypes.js +157 -0
  75. package/dist/RaftTypes.js.map +1 -0
  76. package/dist/RaftUpdateEvents.d.ts +33 -0
  77. package/dist/RaftUpdateEvents.js +46 -0
  78. package/dist/RaftUpdateEvents.js.map +1 -0
  79. package/dist/RaftUpdateManager.d.ts +61 -0
  80. package/dist/RaftUpdateManager.js +618 -0
  81. package/dist/RaftUpdateManager.js.map +1 -0
  82. package/dist/RaftUtils.d.ts +125 -0
  83. package/dist/RaftUtils.js +454 -0
  84. package/dist/RaftUtils.js.map +1 -0
  85. package/dist/RaftWifiTypes.d.ts +23 -0
  86. package/dist/RaftWifiTypes.js +43 -0
  87. package/dist/RaftWifiTypes.js.map +1 -0
  88. package/dist/TestDataGen.d.ts +7 -0
  89. package/dist/TestDataGen.js +133 -0
  90. package/dist/TestDataGen.js.map +1 -0
  91. package/dist/main.d.ts +18 -0
  92. package/dist/main.js +42 -0
  93. package/dist/main.js.map +1 -0
  94. package/eslint.config.mjs +33 -0
  95. package/examples/dashboard/package.json +39 -0
  96. package/examples/dashboard/src/ConnManager.ts +86 -0
  97. package/examples/dashboard/src/Main.tsx +100 -0
  98. package/examples/dashboard/src/StatusScreen.tsx +72 -0
  99. package/examples/dashboard/src/SystemTypeCog/CogStateInfo.ts +144 -0
  100. package/examples/dashboard/src/SystemTypeCog/SystemTypeCog.ts +77 -0
  101. package/examples/dashboard/src/SystemTypeMarty/RICAddOn.ts +70 -0
  102. package/examples/dashboard/src/SystemTypeMarty/RICAddOnBase.ts +33 -0
  103. package/examples/dashboard/src/SystemTypeMarty/RICAddOnManager.ts +342 -0
  104. package/examples/dashboard/src/SystemTypeMarty/RICCommsStats.ts +170 -0
  105. package/examples/dashboard/src/SystemTypeMarty/RICHWElem.ts +123 -0
  106. package/examples/dashboard/src/SystemTypeMarty/RICLEDPatternChecker.ts +207 -0
  107. package/examples/dashboard/src/SystemTypeMarty/RICROSSerial.ts +464 -0
  108. package/examples/dashboard/src/SystemTypeMarty/RICServoFaultDetector.ts +146 -0
  109. package/examples/dashboard/src/SystemTypeMarty/RICStateInfo.ts +32 -0
  110. package/examples/dashboard/src/SystemTypeMarty/RICSystemUtils.ts +371 -0
  111. package/examples/dashboard/src/SystemTypeMarty/RICTypes.ts +20 -0
  112. package/examples/dashboard/src/SystemTypeMarty/SystemTypeMarty.ts +113 -0
  113. package/examples/dashboard/src/index.html +15 -0
  114. package/examples/dashboard/src/index.tsx +15 -0
  115. package/examples/dashboard/src/styles.css +122 -0
  116. package/examples/dashboard/tsconfig.json +18 -0
  117. package/jest.config.js +11 -0
  118. package/package.json +50 -0
  119. package/src/RaftAttributeHandler.ts +289 -0
  120. package/src/RaftChannel.ts +30 -0
  121. package/src/RaftChannelWebBLE.ts +342 -0
  122. package/src/RaftChannelWebSerial.ts +408 -0
  123. package/src/RaftChannelWebSocket.ts +245 -0
  124. package/src/RaftCommsStats.ts +142 -0
  125. package/src/RaftConnEvents.ts +46 -0
  126. package/src/RaftConnector.ts +745 -0
  127. package/src/RaftCustomAttrHandler.ts +54 -0
  128. package/src/RaftDeviceInfo.ts +104 -0
  129. package/src/RaftDeviceManager.ts +542 -0
  130. package/src/RaftDeviceMsg.ts +20 -0
  131. package/src/RaftDeviceStates.ts +89 -0
  132. package/src/RaftFileHandler.ts +668 -0
  133. package/src/RaftLog.ts +70 -0
  134. package/src/RaftMiniHDLC.ts +396 -0
  135. package/src/RaftMsgHandler.ts +778 -0
  136. package/src/RaftMsgTrackInfo.ts +51 -0
  137. package/src/RaftProtocolDefs.ts +46 -0
  138. package/src/RaftStreamHandler.ts +328 -0
  139. package/src/RaftSystemType.ts +25 -0
  140. package/src/RaftSystemUtils.ts +487 -0
  141. package/src/RaftTypes.ts +250 -0
  142. package/src/RaftUpdateEvents.ts +48 -0
  143. package/src/RaftUpdateManager.ts +778 -0
  144. package/src/RaftUtils.ts +484 -0
  145. package/src/RaftWifiTypes.ts +36 -0
  146. package/src/TestDataGen.ts +157 -0
  147. package/src/main.ts +28 -0
  148. package/testdata/TestDeviceTypeRecs.json +492 -0
  149. 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
+ }