sportident.js 1.0.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.
Files changed (151) hide show
  1. package/.dependency-cruiser.js +233 -0
  2. package/.editorconfig +12 -0
  3. package/.eslintignore +6 -0
  4. package/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
  6. package/.github/workflows/npm.yml +17 -0
  7. package/LICENSE +21 -0
  8. package/README.md +31 -0
  9. package/babel.config.js +21 -0
  10. package/build-docs.sh +25 -0
  11. package/docs/index.md +9 -0
  12. package/docs/typedoc/index.md +11 -0
  13. package/eslint.base.js +232 -0
  14. package/install.sh +6 -0
  15. package/jest.config.ts +49 -0
  16. package/nx.json +39 -0
  17. package/package.json +51 -0
  18. package/src/SiCard/BaseSiCard.test.ts +187 -0
  19. package/src/SiCard/BaseSiCard.ts +101 -0
  20. package/src/SiCard/IRaceResultData.ts +16 -0
  21. package/src/SiCard/ISiCard.ts +23 -0
  22. package/src/SiCard/ISiCardExamples.ts +4 -0
  23. package/src/SiCard/index.ts +2 -0
  24. package/src/SiCard/raceResultTools.test.ts +260 -0
  25. package/src/SiCard/raceResultTools.ts +150 -0
  26. package/src/SiCard/types/FCard.test.ts +19 -0
  27. package/src/SiCard/types/FCard.ts +14 -0
  28. package/src/SiCard/types/ModernSiCard.test.ts +186 -0
  29. package/src/SiCard/types/ModernSiCard.ts +241 -0
  30. package/src/SiCard/types/PCard.test.ts +19 -0
  31. package/src/SiCard/types/PCard.ts +14 -0
  32. package/src/SiCard/types/SIAC.test.ts +84 -0
  33. package/src/SiCard/types/SIAC.ts +19 -0
  34. package/src/SiCard/types/SiCard10.test.ts +85 -0
  35. package/src/SiCard/types/SiCard10.ts +17 -0
  36. package/src/SiCard/types/SiCard11.test.ts +84 -0
  37. package/src/SiCard/types/SiCard11.ts +19 -0
  38. package/src/SiCard/types/SiCard5.test.ts +149 -0
  39. package/src/SiCard/types/SiCard5.ts +129 -0
  40. package/src/SiCard/types/SiCard6.test.ts +179 -0
  41. package/src/SiCard/types/SiCard6.ts +222 -0
  42. package/src/SiCard/types/SiCard8.test.ts +137 -0
  43. package/src/SiCard/types/SiCard8.ts +129 -0
  44. package/src/SiCard/types/SiCard9.test.ts +132 -0
  45. package/src/SiCard/types/SiCard9.ts +128 -0
  46. package/src/SiCard/types/TCard.test.ts +19 -0
  47. package/src/SiCard/types/TCard.ts +14 -0
  48. package/src/SiCard/types/index.test.ts +26 -0
  49. package/src/SiCard/types/index.ts +15 -0
  50. package/src/SiCard/types/modernSiCardExamples.ts +364 -0
  51. package/src/SiCard/types/siCard5Examples.ts +73 -0
  52. package/src/SiCard/types/siCard6Examples.ts +262 -0
  53. package/src/SiCard/types/siCard8Examples.ts +152 -0
  54. package/src/SiCard/types/siCard9Examples.ts +143 -0
  55. package/src/SiDevice/INavigatorWebSerial.ts +78 -0
  56. package/src/SiDevice/INavigatorWebUsb.ts +62 -0
  57. package/src/SiDevice/ISiDevice.ts +48 -0
  58. package/src/SiDevice/ISiDeviceDriver.ts +35 -0
  59. package/src/SiDevice/README.md +13 -0
  60. package/src/SiDevice/SiDevice.test.ts +354 -0
  61. package/src/SiDevice/SiDevice.ts +132 -0
  62. package/src/SiDevice/WebSerialSiDeviceDriver.ts +146 -0
  63. package/src/SiDevice/WebUsbSiDeviceDriver.ts +343 -0
  64. package/src/SiDevice/index.ts +3 -0
  65. package/src/SiDevice/testUtils/index.ts +2 -0
  66. package/src/SiDevice/testUtils/testISiDeviceDriver.ts +63 -0
  67. package/src/SiDevice/testUtils/testISiDeviceDriverWithAutodetection.ts +72 -0
  68. package/src/SiStation/BaseSiStation.test.ts +221 -0
  69. package/src/SiStation/BaseSiStation.ts +253 -0
  70. package/src/SiStation/CoupledSiStation.test.ts +41 -0
  71. package/src/SiStation/CoupledSiStation.ts +130 -0
  72. package/src/SiStation/ISiMainStation.ts +29 -0
  73. package/src/SiStation/ISiSendTask.ts +9 -0
  74. package/src/SiStation/ISiStation.ts +88 -0
  75. package/src/SiStation/ISiTargetMultiplexer.ts +51 -0
  76. package/src/SiStation/SiMainStation.test.ts +222 -0
  77. package/src/SiStation/SiMainStation.ts +133 -0
  78. package/src/SiStation/SiSendTask.ts +50 -0
  79. package/src/SiStation/SiTargetMultiplexer.targeting.test.ts +112 -0
  80. package/src/SiStation/SiTargetMultiplexer.test.ts +605 -0
  81. package/src/SiStation/SiTargetMultiplexer.ts +241 -0
  82. package/src/SiStation/index.ts +5 -0
  83. package/src/SiStation/siStationExamples.ts +103 -0
  84. package/src/constants.test.ts +17 -0
  85. package/src/constants.ts +92 -0
  86. package/src/fakes/FakeSiCard/BaseFakeSiCard.test.ts +11 -0
  87. package/src/fakes/FakeSiCard/BaseFakeSiCard.ts +10 -0
  88. package/src/fakes/FakeSiCard/IFakeSiCard.ts +6 -0
  89. package/src/fakes/FakeSiCard/index.ts +2 -0
  90. package/src/fakes/FakeSiCard/types/FakeModernSiCard.test.ts +62 -0
  91. package/src/fakes/FakeSiCard/types/FakeModernSiCard.ts +43 -0
  92. package/src/fakes/FakeSiCard/types/FakeSIAC.ts +17 -0
  93. package/src/fakes/FakeSiCard/types/FakeSiCard10.ts +17 -0
  94. package/src/fakes/FakeSiCard/types/FakeSiCard11.ts +17 -0
  95. package/src/fakes/FakeSiCard/types/FakeSiCard5.test.ts +42 -0
  96. package/src/fakes/FakeSiCard/types/FakeSiCard5.ts +40 -0
  97. package/src/fakes/FakeSiCard/types/FakeSiCard6.test.ts +62 -0
  98. package/src/fakes/FakeSiCard/types/FakeSiCard6.ts +44 -0
  99. package/src/fakes/FakeSiCard/types/FakeSiCard8.ts +16 -0
  100. package/src/fakes/FakeSiCard/types/FakeSiCard9.ts +16 -0
  101. package/src/fakes/FakeSiCard/types/index.ts +7 -0
  102. package/src/fakes/FakeSiDeviceDriver.ts +148 -0
  103. package/src/fakes/FakeSiMainStation.test.ts +141 -0
  104. package/src/fakes/FakeSiMainStation.ts +118 -0
  105. package/src/fakes/IFakeSiMainStation.ts +15 -0
  106. package/src/fakes/index.ts +2 -0
  107. package/src/index.ts +24 -0
  108. package/src/siProtocol.test.ts +509 -0
  109. package/src/siProtocol.ts +417 -0
  110. package/src/storage/SiArray.test.ts +103 -0
  111. package/src/storage/SiArray.ts +56 -0
  112. package/src/storage/SiBool.test.ts +81 -0
  113. package/src/storage/SiBool.ts +47 -0
  114. package/src/storage/SiDataType.test.ts +81 -0
  115. package/src/storage/SiDataType.ts +60 -0
  116. package/src/storage/SiDict.test.ts +115 -0
  117. package/src/storage/SiDict.ts +60 -0
  118. package/src/storage/SiEnum.test.ts +77 -0
  119. package/src/storage/SiEnum.ts +48 -0
  120. package/src/storage/SiFieldValue.test.ts +58 -0
  121. package/src/storage/SiFieldValue.ts +23 -0
  122. package/src/storage/SiInt.test.ts +80 -0
  123. package/src/storage/SiInt.ts +84 -0
  124. package/src/storage/SiModified.test.ts +135 -0
  125. package/src/storage/SiModified.ts +59 -0
  126. package/src/storage/SiStorage.test.ts +51 -0
  127. package/src/storage/SiStorage.ts +44 -0
  128. package/src/storage/index.test.ts +222 -0
  129. package/src/storage/index.ts +12 -0
  130. package/src/storage/interfaces.ts +41 -0
  131. package/src/storage/siStringEncoding.ts +1361 -0
  132. package/src/testUtils.test.ts +266 -0
  133. package/src/testUtils.ts +75 -0
  134. package/src/utils/NumberRange.test.ts +66 -0
  135. package/src/utils/NumberRange.ts +46 -0
  136. package/src/utils/NumberRangeRegistry.test.ts +49 -0
  137. package/src/utils/NumberRangeRegistry.ts +43 -0
  138. package/src/utils/bytes.test.ts +126 -0
  139. package/src/utils/bytes.ts +69 -0
  140. package/src/utils/errors.test.ts +29 -0
  141. package/src/utils/errors.ts +20 -0
  142. package/src/utils/events.test.ts +112 -0
  143. package/src/utils/events.ts +68 -0
  144. package/src/utils/general.test.ts +139 -0
  145. package/src/utils/general.ts +69 -0
  146. package/src/utils/index.ts +8 -0
  147. package/src/utils/mixins.test.ts +40 -0
  148. package/src/utils/mixins.ts +13 -0
  149. package/src/utils/typed.ts +3 -0
  150. package/tsconfig.base.json +22 -0
  151. package/tsconfig.json +15 -0
@@ -0,0 +1,354 @@
1
+ import { describe, expect, test } from '@jest/globals';
2
+ import * as testUtils from '../testUtils';
3
+ import type { ISiDeviceDriver } from './ISiDeviceDriver';
4
+ import { DeviceClosedError, type ISiDeviceDriverData, SiDeviceState, SiDeviceStateChangeEvent } from './ISiDevice';
5
+ import { SiDevice } from './SiDevice';
6
+
7
+ testUtils.useFakeTimers();
8
+
9
+ function mockDriver(driver: Partial<ISiDeviceDriver<ISiDeviceDriverData<unknown>>>) {
10
+ return driver as unknown as ISiDeviceDriver<ISiDeviceDriverData<unknown>>;
11
+ }
12
+
13
+ describe('SiDevice', () => {
14
+ test('state management', () => {
15
+ const siDevice = new SiDevice('stateManagement', {
16
+ driver: mockDriver({ name: 'FakeDriver' })
17
+ });
18
+ const stateChanges: SiDeviceState[] = [];
19
+ const onStateChange = (e: SiDeviceStateChangeEvent) => {
20
+ stateChanges.push(e.state);
21
+ };
22
+ siDevice.addEventListener('stateChange', onStateChange);
23
+ expect(stateChanges).toEqual([]);
24
+ expect(siDevice.state).toBe(SiDeviceState.Closed);
25
+ siDevice.setState(SiDeviceState.Closed);
26
+ expect(stateChanges).toEqual([]);
27
+ expect(siDevice.state).toBe(SiDeviceState.Closed);
28
+ siDevice.setState(SiDeviceState.Opening);
29
+ expect(stateChanges).toEqual([SiDeviceState.Opening]);
30
+ expect(siDevice.state).toBe(SiDeviceState.Opening);
31
+ siDevice.setState(SiDeviceState.Opened);
32
+ expect(stateChanges).toEqual([SiDeviceState.Opening, SiDeviceState.Opened]);
33
+ expect(siDevice.state).toBe(SiDeviceState.Opened);
34
+ siDevice.removeEventListener('stateChange', onStateChange);
35
+ });
36
+ test('fails opening while opening', (done) => {
37
+ const data = {
38
+ driver: mockDriver({})
39
+ };
40
+ const siDevice = new SiDevice('receiveLoopFail', data);
41
+
42
+ siDevice.setState(SiDeviceState.Opening);
43
+ siDevice.open().catch(() => done());
44
+ });
45
+ test('fails opening while closing', (done) => {
46
+ let count = 0;
47
+ const siDevice = new SiDevice('receiveLoopFail', {
48
+ driver: mockDriver({
49
+ receive: (_device: unknown) => {
50
+ if (count > 2) {
51
+ throw new DeviceClosedError('test');
52
+ }
53
+ count += 1;
54
+ throw new Error('test');
55
+ }
56
+ })
57
+ });
58
+
59
+ siDevice.setState(SiDeviceState.Closing);
60
+ siDevice.open().catch(() => done());
61
+ });
62
+ test('immediately opens when opened', (done) => {
63
+ let count = 0;
64
+ const data = {
65
+ driver: mockDriver({
66
+ receive: (_device: unknown) => {
67
+ if (count > 2) {
68
+ throw new DeviceClosedError('test');
69
+ }
70
+ count += 1;
71
+ throw new Error('test');
72
+ }
73
+ })
74
+ };
75
+ const siDevice = new SiDevice('receiveLoopFail', data);
76
+
77
+ siDevice.setState(SiDeviceState.Opened);
78
+ siDevice.open().then((openedDevice) => {
79
+ expect(openedDevice).toBe(siDevice);
80
+ done();
81
+ });
82
+ });
83
+ test('opens when closed', (done) => {
84
+ let opened = false;
85
+ const data = {
86
+ driver: mockDriver({
87
+ open: (_device: unknown) => {
88
+ opened = true;
89
+ return Promise.resolve();
90
+ }
91
+ })
92
+ };
93
+ const siDevice = new SiDevice('receiveLoopFail', data);
94
+
95
+ siDevice.setState(SiDeviceState.Closed);
96
+ siDevice.open().then((openedDevice) => {
97
+ expect(openedDevice).toBe(siDevice);
98
+ expect(opened).toBe(true);
99
+ done();
100
+ });
101
+ });
102
+ test('opening can fail', (done) => {
103
+ const data = {
104
+ driver: mockDriver({
105
+ open: (_device: unknown) => {
106
+ throw new Error('test');
107
+ }
108
+ })
109
+ };
110
+ const siDevice = new SiDevice('receiveLoopFail', data);
111
+
112
+ siDevice.setState(SiDeviceState.Closed);
113
+ siDevice.open().catch((err) => {
114
+ expect(err.message).toBe('test');
115
+ done();
116
+ });
117
+ });
118
+ test('opening can reject', (done) => {
119
+ const data = {
120
+ driver: mockDriver({
121
+ open: (_device: unknown) => Promise.reject(new Error('test'))
122
+ })
123
+ };
124
+ const siDevice = new SiDevice('receiveLoopFail', data);
125
+
126
+ siDevice.setState(SiDeviceState.Closed);
127
+ siDevice.open().catch((err) => {
128
+ expect(err.message).toBe('test');
129
+ done();
130
+ });
131
+ });
132
+ test('fails closing while opening', (done) => {
133
+ const data = {
134
+ driver: mockDriver({})
135
+ };
136
+ const siDevice = new SiDevice('receiveLoopFail', data);
137
+
138
+ siDevice.setState(SiDeviceState.Opening);
139
+ siDevice.close().catch(() => done());
140
+ });
141
+ test('fails closing while closing', (done) => {
142
+ const data = {
143
+ driver: mockDriver({})
144
+ };
145
+ const siDevice = new SiDevice('receiveLoopFail', data);
146
+
147
+ siDevice.setState(SiDeviceState.Closing);
148
+ siDevice.close().catch(() => done());
149
+ });
150
+ test('immediately closes when closed', (done) => {
151
+ const data = {
152
+ driver: mockDriver({})
153
+ };
154
+ const siDevice = new SiDevice('receiveLoopFail', data);
155
+
156
+ siDevice.setState(SiDeviceState.Closed);
157
+ siDevice.close().then((openedDevice) => {
158
+ expect(openedDevice).toBe(siDevice);
159
+ done();
160
+ });
161
+ });
162
+ test('closes when opened', (done) => {
163
+ let opened = true;
164
+ const data = {
165
+ driver: mockDriver({
166
+ close: (_device: unknown) => {
167
+ opened = false;
168
+ return Promise.resolve();
169
+ }
170
+ })
171
+ };
172
+ const siDevice = new SiDevice('receiveLoopFail', data);
173
+
174
+ siDevice.setState(SiDeviceState.Opened);
175
+ siDevice.close().then((openedDevice) => {
176
+ expect(openedDevice).toBe(siDevice);
177
+ expect(opened).toBe(false);
178
+ done();
179
+ });
180
+ });
181
+ test('closing can fail', (done) => {
182
+ const data = {
183
+ driver: mockDriver({
184
+ close: (_device: unknown) => {
185
+ throw new Error('test');
186
+ }
187
+ })
188
+ };
189
+ const siDevice = new SiDevice('receiveLoopFail', data);
190
+
191
+ siDevice.setState(SiDeviceState.Opened);
192
+ siDevice.close().catch((err) => {
193
+ expect(err.message).toBe('test');
194
+ done();
195
+ });
196
+ });
197
+ test('closing can reject', (done) => {
198
+ const data = {
199
+ driver: mockDriver({
200
+ close: (_device: unknown) => Promise.reject(new Error('test'))
201
+ })
202
+ };
203
+ const siDevice = new SiDevice('receiveLoopFail', data);
204
+
205
+ siDevice.setState(SiDeviceState.Opened);
206
+ siDevice.close().catch((err) => {
207
+ expect(err.message).toBe('test');
208
+ done();
209
+ });
210
+ });
211
+ test('receiveLoop succeed', async () => {
212
+ let count = 0;
213
+ const data = {
214
+ driver: mockDriver({
215
+ receive: (_device: unknown) =>
216
+ new Promise((resolve) => {
217
+ if (count > 2) {
218
+ throw new DeviceClosedError('test');
219
+ }
220
+ setTimeout(() => {
221
+ count += 1;
222
+ resolve([0x12]);
223
+ }, 1);
224
+ })
225
+ })
226
+ };
227
+ const siDevice = new SiDevice('receiveLoopSucceed', data);
228
+
229
+ const received: number[][] = [];
230
+ siDevice.addEventListener('receive', (e) => {
231
+ received.push(e.uint8Data);
232
+ });
233
+ siDevice.setState(SiDeviceState.Opened);
234
+ siDevice.receiveLoop();
235
+
236
+ await testUtils.nTimesAsync(1, () => testUtils.advanceTimersByTime(1));
237
+ expect(count).toBe(1);
238
+ expect(received).toEqual([[0x12]]);
239
+
240
+ await testUtils.nTimesAsync(1, () => testUtils.advanceTimersByTime(1));
241
+ expect(count).toBe(2);
242
+ expect(received).toEqual([[0x12], [0x12]]);
243
+ });
244
+ test('receiveLoop can fail', async () => {
245
+ let count = 0;
246
+ const data = {
247
+ driver: mockDriver({
248
+ receive: (_device: unknown) => {
249
+ if (count > 2) {
250
+ throw new DeviceClosedError('test');
251
+ }
252
+ count += 1;
253
+ throw new Error('test');
254
+ }
255
+ })
256
+ };
257
+ const siDevice = new SiDevice('receiveLoopFail', data);
258
+
259
+ const received: number[][] = [];
260
+ siDevice.addEventListener('receive', (e) => {
261
+ received.push(e.uint8Data);
262
+ });
263
+ siDevice.setState(SiDeviceState.Opened);
264
+ siDevice.receiveLoop();
265
+
266
+ await testUtils.nTimesAsync(1, () => testUtils.advanceTimersByTime(1));
267
+ expect(count).toBe(1);
268
+ expect(received).toEqual([]);
269
+
270
+ await testUtils.nTimesAsync(1, () => testUtils.advanceTimersByTime(100));
271
+ expect(count).toBe(2);
272
+ expect(received).toEqual([]);
273
+ });
274
+ test('receiveLoop can reject', async () => {
275
+ let count = 0;
276
+ const data = {
277
+ driver: mockDriver({
278
+ receive: (_device: unknown) => {
279
+ if (count > 2) {
280
+ throw new DeviceClosedError('test');
281
+ }
282
+ count += 1;
283
+ return Promise.reject(new Error('test'));
284
+ }
285
+ })
286
+ };
287
+ const siDevice = new SiDevice('receiveLoopReject', data);
288
+
289
+ const received: number[][] = [];
290
+ siDevice.addEventListener('receive', (e) => {
291
+ received.push(e.uint8Data);
292
+ });
293
+ siDevice.setState(SiDeviceState.Opened);
294
+ siDevice.receiveLoop();
295
+
296
+ await testUtils.nTimesAsync(1, () => testUtils.advanceTimersByTime(1));
297
+ expect(count).toBe(1);
298
+ expect(received).toEqual([]);
299
+
300
+ await testUtils.nTimesAsync(1, () => testUtils.advanceTimersByTime(100));
301
+ expect(count).toBe(2);
302
+ expect(received).toEqual([]);
303
+ });
304
+ test('receiveLoop can fail if device closed', async () => {
305
+ let count = 0;
306
+ const data = {
307
+ driver: mockDriver({
308
+ receive: (_device: unknown) => {
309
+ count += 1;
310
+ throw new DeviceClosedError('test');
311
+ }
312
+ })
313
+ };
314
+ const siDevice = new SiDevice('receiveLoopFailIfClosed', data);
315
+
316
+ const received: number[][] = [];
317
+ siDevice.addEventListener('receive', (e) => {
318
+ received.push(e.uint8Data);
319
+ });
320
+ siDevice.setState(SiDeviceState.Opened);
321
+ siDevice.receiveLoop();
322
+
323
+ await testUtils.nTimesAsync(1, () => testUtils.advanceTimersByTime(1));
324
+ expect(count).toBe(1);
325
+ expect(received).toEqual([]);
326
+
327
+ await testUtils.nTimesAsync(1, () => testUtils.advanceTimersByTime(100));
328
+ expect(count).toBe(1);
329
+ expect(received).toEqual([]);
330
+ });
331
+
332
+ test('can send', async () => {
333
+ const sent: number[][] = [];
334
+ const data = {
335
+ driver: mockDriver({
336
+ send: (_device: unknown, buffer: number[]) => {
337
+ sent.push(buffer);
338
+ return Promise.resolve();
339
+ }
340
+ })
341
+ };
342
+ const siDevice = new SiDevice('canSend', data);
343
+
344
+ siDevice.setState(SiDeviceState.Opened);
345
+ siDevice.send([0x34]);
346
+
347
+ await testUtils.nTimesAsync(1, () => testUtils.advanceTimersByTime(0));
348
+ expect(sent).toEqual([[0x34]]);
349
+ siDevice.send([0x56]);
350
+
351
+ await testUtils.nTimesAsync(1, () => testUtils.advanceTimersByTime(100));
352
+ expect(sent).toEqual([[0x34], [0x56]]);
353
+ });
354
+ });
@@ -0,0 +1,132 @@
1
+ import { DeviceClosedError, type ISiDevice, type ISiDeviceDriverData, type SiDeviceEvents, SiDeviceReceiveEvent, SiDeviceState, SiDeviceStateChangeEvent } from './ISiDevice';
2
+ import * as utils from '../utils';
3
+ import type { ISiDeviceDriver } from './ISiDeviceDriver';
4
+
5
+ export class SiDevice<T extends ISiDeviceDriverData<ISiDeviceDriver<T>>> implements ISiDevice<T> {
6
+ name: string;
7
+ ident: string;
8
+ data: T;
9
+ private internalState: SiDeviceState;
10
+
11
+ constructor(typeSpecificIdent: string, data: T) {
12
+ this.data = data;
13
+ this.name = `${data.driver.name}(${typeSpecificIdent})`;
14
+ this.ident = `${data.driver.name}-${typeSpecificIdent}`;
15
+ this.internalState = SiDeviceState.Closed;
16
+ }
17
+
18
+ get state(): SiDeviceState {
19
+ return this.internalState;
20
+ }
21
+
22
+ setState(newState: SiDeviceState): void {
23
+ if (newState !== this.internalState) {
24
+ this.internalState = newState;
25
+ this.dispatchEvent('stateChange', new SiDeviceStateChangeEvent(this, newState));
26
+ }
27
+ }
28
+
29
+ open(): Promise<SiDevice<T>> {
30
+ if (this.state === SiDeviceState.Closing) {
31
+ return Promise.reject(new Error(`Cannot open closing ${this.constructor.name}`));
32
+ }
33
+ if (this.state === SiDeviceState.Opening) {
34
+ return Promise.reject(new Error(`Cannot open opening ${this.constructor.name}`));
35
+ }
36
+ if (this.state === SiDeviceState.Opened) {
37
+ return Promise.resolve(this);
38
+ }
39
+ this.setState(SiDeviceState.Opening);
40
+ try {
41
+ return this.data.driver
42
+ .open(this)
43
+ .then(() => {
44
+ console.debug('Starting Receive Loop...');
45
+ this.receiveLoop();
46
+ this.setState(SiDeviceState.Opened);
47
+ return this;
48
+ })
49
+ .catch((err: Error) => {
50
+ this.setState(SiDeviceState.Closed);
51
+ throw err;
52
+ });
53
+ } catch (err) {
54
+ return Promise.reject(err);
55
+ }
56
+ }
57
+
58
+ close(): Promise<SiDevice<T>> {
59
+ if (this.state === SiDeviceState.Closing) {
60
+ return Promise.reject(new Error(`Cannot close closing ${this.constructor.name}`));
61
+ }
62
+ if (this.state === SiDeviceState.Opening) {
63
+ return Promise.reject(new Error(`Cannot close opening ${this.constructor.name}`));
64
+ }
65
+ if (this.state === SiDeviceState.Closed) {
66
+ return Promise.resolve(this);
67
+ }
68
+ this.setState(SiDeviceState.Closing);
69
+ try {
70
+ return this.data.driver
71
+ .close(this)
72
+ .then(() => {
73
+ this.setState(SiDeviceState.Closed);
74
+ return this;
75
+ })
76
+ .catch((err: Error) => {
77
+ this.setState(SiDeviceState.Closed);
78
+ throw err;
79
+ });
80
+ } catch (err) {
81
+ return Promise.reject(err);
82
+ }
83
+ }
84
+
85
+ receiveLoop(): void {
86
+ try {
87
+ // Stop receive loop when closing
88
+ if (this.internalState == SiDeviceState.Closing || this.internalState == SiDeviceState.Closed) {
89
+ return;
90
+ }
91
+ this.receive()
92
+ .then((uint8Data) => {
93
+ console.debug(`<= (${this.name})\n${utils.prettyHex(uint8Data, 16)}`);
94
+ this.dispatchEvent('receive', new SiDeviceReceiveEvent(this, uint8Data));
95
+ })
96
+ .catch((err: Error) => {
97
+ if (this.shouldStopReceivingBecauseOfError(err)) {
98
+ console.warn('Receive loop stopped while receiving');
99
+ throw err;
100
+ }
101
+ console.warn(`${this.name}: Error receiving: ${err.message}`);
102
+ return utils.waitFor(100);
103
+ })
104
+ .then(() => this.receiveLoop())
105
+ .catch(() => undefined);
106
+ } catch (exc: unknown) {
107
+ const err = utils.getErrorOrThrow(exc);
108
+ console.warn(`${this.name}: Error starting receiving: ${err.message}`);
109
+ if (this.shouldStopReceivingBecauseOfError(err)) {
110
+ console.warn('Receive loop stopped while starting receiving');
111
+ return;
112
+ }
113
+ utils.waitFor(100).then(() => this.receiveLoop());
114
+ }
115
+ }
116
+
117
+ shouldStopReceivingBecauseOfError(error: unknown): boolean {
118
+ return error instanceof DeviceClosedError || error instanceof utils.NotImplementedError;
119
+ }
120
+
121
+ receive(): Promise<number[]> {
122
+ return this.data.driver.receive(this);
123
+ }
124
+
125
+ send(buffer: number[]): Promise<unknown> {
126
+ console.debug(`=> (${this.name})\n${utils.prettyHex(buffer, 16)}`);
127
+ return this.data.driver.send(this, buffer);
128
+ }
129
+ }
130
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface,@typescript-eslint/no-unused-vars
131
+ export interface SiDevice<T extends ISiDeviceDriverData<ISiDeviceDriver<T>>> extends utils.EventTarget<SiDeviceEvents> {}
132
+ utils.applyMixins(SiDevice, [utils.EventTarget]);
@@ -0,0 +1,146 @@
1
+ import * as utils from '../utils';
2
+ import type { ISiDevice, ISiDeviceDriverData } from '../SiDevice/ISiDevice';
3
+ import type { ISiDeviceDriver, SiDeviceDriverWithAutodetectionEvents } from '../SiDevice/ISiDeviceDriver';
4
+ import { SiDeviceRemoveEvent } from '../SiDevice/ISiDeviceDriver';
5
+ import { SiDevice } from '../SiDevice/SiDevice';
6
+ import type * as nav from './INavigatorWebSerial';
7
+
8
+ const siDeviceFilters = [{ usbVendorId: 0x10c4, usbProductId: 0x800a }];
9
+ const matchesSiDeviceFilters = (usbVendorId: number, usbProductId: number) => siDeviceFilters.some((filter) => usbVendorId === filter.usbVendorId && usbProductId === filter.usbProductId);
10
+
11
+ const getIdent = (device: nav.WebSerialDevice) => `${device.getInfo().usbProductId}`;
12
+
13
+ export interface WebSerialSiDeviceDriverData extends ISiDeviceDriverData<WebSerialSiDeviceDriver> {
14
+ driver: WebSerialSiDeviceDriver;
15
+ device: nav.WebSerialDevice;
16
+ }
17
+
18
+ export type IWebSerialSiDevice = ISiDevice<WebSerialSiDeviceDriverData>;
19
+ export type WebSerialSiDevice = SiDevice<WebSerialSiDeviceDriverData>;
20
+
21
+ class WebSerialSiDeviceDriver implements ISiDeviceDriver<WebSerialSiDeviceDriverData> {
22
+ public name = 'WebSerial';
23
+
24
+ private siDeviceByIdent: { [ident: string]: WebSerialSiDevice } = {};
25
+
26
+ private autodetectedSiDevices: { [ident: string]: WebSerialSiDevice } = {};
27
+
28
+ private reader: ReadableStreamDefaultReader<any> | undefined;
29
+
30
+ constructor(private navigatorSerial: nav.Serial) {}
31
+
32
+ detect(): Promise<WebSerialSiDevice> {
33
+ return this.navigatorSerial
34
+ .requestPort({
35
+ filters: siDeviceFilters
36
+ })
37
+ .catch((e) => {
38
+ return Promise.reject(new Error('Failed to get access to Serial device!' + e.toString()));
39
+ })
40
+ .then((navigatorWebSerialDevice: nav.WebSerialDevice) => {
41
+ if (!matchesSiDeviceFilters(navigatorWebSerialDevice.getInfo().usbVendorId || 0, navigatorWebSerialDevice.getInfo().usbProductId || 0)) {
42
+ return Promise.reject(new Error('Not a SI device'));
43
+ }
44
+ const ident = getIdent(navigatorWebSerialDevice);
45
+ if (this.autodetectedSiDevices[ident] !== undefined) {
46
+ return Promise.reject(new Error('Duplicate SI device'));
47
+ }
48
+ const siDevice = this.getSiDevice(navigatorWebSerialDevice);
49
+ this.autodetectedSiDevices[ident] = siDevice;
50
+ return siDevice.open();
51
+ });
52
+ }
53
+
54
+ getSiDevice(navigatorWebSerialDevice: nav.WebSerialDevice): WebSerialSiDevice {
55
+ const ident = getIdent(navigatorWebSerialDevice);
56
+ if (this.siDeviceByIdent[ident] !== undefined) {
57
+ return this.siDeviceByIdent[ident];
58
+ }
59
+ const newSiDeviceData: WebSerialSiDeviceDriverData = {
60
+ driver: this,
61
+ device: navigatorWebSerialDevice
62
+ };
63
+ const newSiDevice = new SiDevice(ident, newSiDeviceData);
64
+ this.siDeviceByIdent[ident] = newSiDevice;
65
+ return newSiDevice;
66
+ }
67
+
68
+ forgetSiDevice(siDevice: IWebSerialSiDevice) {
69
+ const navigatorWebSerialDevice = siDevice.data.device;
70
+ const ident = getIdent(navigatorWebSerialDevice);
71
+ delete this.siDeviceByIdent[ident];
72
+ if (this.autodetectedSiDevices[ident] !== undefined) {
73
+ this.dispatchEvent('remove', new SiDeviceRemoveEvent(siDevice));
74
+ }
75
+ delete this.autodetectedSiDevices[ident];
76
+ }
77
+
78
+ open(device: IWebSerialSiDevice): Promise<void> {
79
+ console.debug('Opening...');
80
+ const navigatorDevice = device.data.device;
81
+ return navigatorDevice.open({ baudRate: 38400 })
82
+ .catch((e) => {
83
+ return Promise.reject(new Error("Failed to open serial device"))
84
+ });
85
+ }
86
+
87
+ async close(device: IWebSerialSiDevice): Promise<void> {
88
+ console.debug('Disabling Serial...');
89
+ const navigatorDevice = device.data.device;
90
+ try {
91
+ if (this.reader != undefined) {
92
+ this.reader.releaseLock();
93
+ }
94
+ } catch (e) {
95
+ console.warn(e);
96
+ }
97
+ this.forgetSiDevice(device);
98
+ await navigatorDevice.readable.cancel();
99
+ await navigatorDevice.close();
100
+ }
101
+
102
+ async receive(device: IWebSerialSiDevice): Promise<number[]> {
103
+ const navigatorDevice = device.data.device;
104
+ this.reader = navigatorDevice.readable.getReader();
105
+ try {
106
+ while (true) {
107
+ const { value, done } = await this.reader.read();
108
+ if (done) {
109
+ if (value == undefined) {
110
+ return [];
111
+ }
112
+ return value;
113
+ }
114
+ if (value != undefined) {
115
+ return value;
116
+ }
117
+ }
118
+ } catch (error) {
119
+ console.warn(error);
120
+ } finally {
121
+ this.reader.releaseLock();
122
+ }
123
+ return [];
124
+ }
125
+
126
+ async send(device: IWebSerialSiDevice, uint8Data: number[]): Promise<void> {
127
+ const navigatorDevice = device.data.device;
128
+ const buffer = new Uint8Array(uint8Data);
129
+ if (navigatorDevice.writable == null) {
130
+ console.error('SiStation is not writable!');
131
+ return;
132
+ }
133
+ const writer = navigatorDevice.writable.getWriter();
134
+ await writer
135
+ .write(buffer)
136
+ .then(() => {
137
+ return writer.releaseLock();
138
+ })
139
+ .then(() => true);
140
+ }
141
+ }
142
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
143
+ interface WebSerialSiDeviceDriver extends utils.EventTarget<SiDeviceDriverWithAutodetectionEvents<WebSerialSiDeviceDriverData>> {}
144
+ utils.applyMixins(WebSerialSiDeviceDriver, [utils.EventTarget]);
145
+
146
+ export const getWebSerialSiDeviceDriver = (navigatorWebSerial: Serial): WebSerialSiDeviceDriver => new WebSerialSiDeviceDriver(<nav.Serial>(<unknown>navigatorWebSerial));