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,88 @@
1
+ import type { SiTargetMultiplexerTarget } from './ISiTargetMultiplexer';
2
+
3
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
4
+ export interface ISiStation<T extends SiTargetMultiplexerTarget> {
5
+ multiplexerTarget: T;
6
+ ident: string;
7
+ }
8
+
9
+ export const SiStationMode = {
10
+ SIACSpecialFunction1: 0x01, // Source: SI Config+
11
+ // 7B=battery test, 7C=on, 7D=off, 7F=radio readout
12
+ Control: 0x02, // Source: SI Config+
13
+ Start: 0x03, // Source: SI Config+
14
+ Finish: 0x04, // Source: SI Config+
15
+ Readout: 0x05, // Source: SI Config+
16
+ Clear: 0x07, // Source: SI Config+
17
+ Check: 0x0a, // Source: SI Config+
18
+ Print: 0x0b, // Source: old SI Config
19
+ StartWithTimeTrigger: 0x0c, // Source: old SI Config
20
+ FinishWithTimeTrigger: 0x0d, // Source: old SI Config
21
+ SIACSpecialFunction2: 0x11, // Source: SI Config+
22
+ // 7C=test
23
+ BCControl: 0x12, // Source: old SI Config
24
+ BCStart: 0x13, // Source: old SI Config
25
+ BCFinish: 0x14, // Source: old SI Config
26
+ BCSlave: 0x1f, // Source: old SI Config
27
+ BeaconControl: 0x32, // Source: SI Config+
28
+ BeaconStart: 0x33, // Source: SI Config+
29
+ BeaconFinish: 0x34 // Source: SI Config+
30
+ };
31
+
32
+ /* eslint-disable no-unused-vars,no-shadow */
33
+ export enum SiStationType {
34
+ Main = 0x00,
35
+ Sprint = 0x01,
36
+ Print = 0x02,
37
+ Field = 0x03,
38
+ Master = 0x04
39
+ }
40
+ /* eslint-enable no-unused-vars,no-shadow */
41
+
42
+ export const SiStationModel = {
43
+ BSF3: 0x8003,
44
+ BSF4: 0x8004,
45
+ BSF5: 0x8115,
46
+ BSF6: 0x8146,
47
+ BSF7A: 0x8117,
48
+ BSF7B: 0x8197,
49
+ BSF8A: 0x8118,
50
+ BSF8B: 0x8198,
51
+ BS7Master: 0x8187,
52
+ BS8Master: 0x8188,
53
+ BSM4: 0x8084,
54
+ BSM6: 0x8086,
55
+ BSM7: 0x9197,
56
+ BSM8: 0x9198,
57
+ BS7S: 0x9597,
58
+ BS7P: 0xb197,
59
+ BS7GSM: 0xb897,
60
+ BS8P: 0xb198
61
+ };
62
+
63
+ interface SiStationModelInfo {
64
+ description: string;
65
+ type: SiStationType;
66
+ series: number;
67
+ }
68
+
69
+ export const SI_STATION_MODEL_INFO: { [model in keyof typeof SiStationModel]: SiStationModelInfo } = {
70
+ BSF3: { description: 'BSF3', type: SiStationType.Field, series: 3 },
71
+ BSF4: { description: 'BSF4', type: SiStationType.Field, series: 4 },
72
+ BSF5: { description: 'BSF5', type: SiStationType.Field, series: 5 },
73
+ BSF6: { description: 'BSF6', type: SiStationType.Field, series: 6 },
74
+ BSF7A: { description: 'BSF7', type: SiStationType.Field, series: 7 },
75
+ BSF7B: { description: 'BSF7', type: SiStationType.Field, series: 7 },
76
+ BSF8A: { description: 'BSF8', type: SiStationType.Field, series: 8 },
77
+ BSF8B: { description: 'BSF8', type: SiStationType.Field, series: 8 },
78
+ BS7Master: { description: 'BS7-Master', type: SiStationType.Master, series: 7 },
79
+ BS8Master: { description: 'BS8-Master', type: SiStationType.Master, series: 8 },
80
+ BSM4: { description: 'BSM4', type: SiStationType.Main, series: 4 },
81
+ BSM6: { description: 'BSM6', type: SiStationType.Main, series: 6 },
82
+ BSM7: { description: 'BSM7', type: SiStationType.Main, series: 7 },
83
+ BSM8: { description: 'BSM8', type: SiStationType.Main, series: 8 },
84
+ BS7S: { description: 'BS7-S', type: SiStationType.Sprint, series: 7 },
85
+ BS7P: { description: 'BS7-P', type: SiStationType.Print, series: 7 },
86
+ BS7GSM: { description: 'BS7-GSM', type: SiStationType.Field, series: 7 },
87
+ BS8P: { description: 'BS8-P', type: SiStationType.Print, series: 8 }
88
+ };
@@ -0,0 +1,51 @@
1
+ import * as utils from '../utils';
2
+ import type * as siProtocol from '../siProtocol';
3
+ import type { ISiDevice, ISiDeviceDriverData } from '../SiDevice/ISiDevice';
4
+ import type { ISiStation } from './ISiStation';
5
+ import type { SiSendTask } from './SiSendTask';
6
+
7
+ export interface ISiTargetMultiplexer extends utils.IEventTarget<SiTargetMultiplexerEvents> {
8
+ _test: _ISiTargetMultiplexerTestData;
9
+ stations: { [Target in SiTargetMultiplexerTarget]?: ISiStation<Target> };
10
+ target: SiTargetMultiplexerTarget;
11
+ latestTarget: SiTargetMultiplexerTarget;
12
+ siDevice: ISiDevice<ISiDeviceDriverData<unknown>>;
13
+ sendMessage: (target: SiTargetMultiplexerTarget, message: siProtocol.SiMessage, numResponses?: number, timeoutInMiliseconds?: number) => Promise<number[][]>;
14
+ sendMessageToLatestTarget: (message: siProtocol.SiMessage, numResponses: number | undefined, timeoutInMiliseconds: number | undefined) => Promise<number[][]>;
15
+ }
16
+
17
+ export interface _ISiTargetMultiplexerTestData {
18
+ latestTarget: SiTargetMultiplexerTarget;
19
+ sendQueue: SiSendTask[];
20
+ }
21
+
22
+ /* eslint-disable no-unused-vars,no-shadow */
23
+ export enum SiTargetMultiplexerTarget {
24
+ Unknown = 0,
25
+ Switching = 1,
26
+ Direct = 2,
27
+ Remote = 3
28
+ }
29
+ /* eslint-enable no-unused-vars,no-shadow */
30
+
31
+ export class SiTargetMultiplexerMessageEvent extends utils.Event<'message'> {
32
+ constructor(public siTargetMultiplexer: ISiTargetMultiplexer, public message: siProtocol.SiMessage) {
33
+ super();
34
+ }
35
+ }
36
+ export class SiTargetMultiplexerDirectMessageEvent extends utils.Event<'directMessage'> {
37
+ constructor(public siTargetMultiplexer: ISiTargetMultiplexer, public message: siProtocol.SiMessage) {
38
+ super();
39
+ }
40
+ }
41
+ export class SiTargetMultiplexerRemoteMessageEvent extends utils.Event<'remoteMessage'> {
42
+ constructor(public siTargetMultiplexer: ISiTargetMultiplexer, public message: siProtocol.SiMessage) {
43
+ super();
44
+ }
45
+ }
46
+
47
+ export type SiTargetMultiplexerEvents = {
48
+ message: SiTargetMultiplexerMessageEvent;
49
+ directMessage: SiTargetMultiplexerDirectMessageEvent;
50
+ remoteMessage: SiTargetMultiplexerRemoteMessageEvent;
51
+ };
@@ -0,0 +1,222 @@
1
+ import { describe, expect, test } from '@jest/globals';
2
+ import _ from 'lodash';
3
+ import { proto } from '../constants';
4
+ import type * as siProtocol from '../siProtocol';
5
+ import * as testUtils from '../testUtils';
6
+ import type { ISiCard } from '../SiCard/ISiCard';
7
+ import type { ISiDevice, ISiDeviceDriverData } from '../SiDevice/ISiDevice';
8
+ import type { ISiDeviceDriver } from '../SiDevice/ISiDeviceDriver';
9
+ import { SiDevice } from '../SiDevice/SiDevice';
10
+ import { SiMainStationSiCardInsertedEvent, SiMainStationSiCardObservedEvent, SiMainStationSiCardRemovedEvent } from './ISiMainStation';
11
+ import { type ISiTargetMultiplexer, SiTargetMultiplexerMessageEvent, SiTargetMultiplexerTarget } from './ISiTargetMultiplexer';
12
+ import { SiTargetMultiplexer } from './SiTargetMultiplexer';
13
+ import { siStationStorageDefinition } from './BaseSiStation';
14
+ import { getBSM8Station } from './siStationExamples';
15
+ import { SiMainStation } from './SiMainStation';
16
+ import { getSiCard5Examples } from '../SiCard/types/siCard5Examples';
17
+ import { getSiCard6Examples } from '../SiCard/types/siCard6Examples';
18
+ import { FakeSiCard5 } from '../fakes/FakeSiCard/types/FakeSiCard5';
19
+ import { FakeSiCard6 } from '../fakes/FakeSiCard/types/FakeSiCard6';
20
+
21
+ testUtils.useFakeTimers();
22
+
23
+ function mockDriver(driver: Partial<ISiDeviceDriver<ISiDeviceDriverData<unknown>>>) {
24
+ return driver as unknown as ISiDeviceDriver<ISiDeviceDriverData<unknown>>;
25
+ }
26
+
27
+ describe('SiMainStation', () => {
28
+ test('fromSiDevice', () => {
29
+ const fakeSiDevice = new SiDevice('fromSiDevice', {
30
+ driver: mockDriver({ name: 'FakeSiDevice' })
31
+ });
32
+ const myMainStation1 = SiMainStation.fromSiDevice(fakeSiDevice);
33
+ expect(myMainStation1 instanceof SiMainStation).toBe(true);
34
+ expect(myMainStation1.ident).toBe('Direct-FakeSiDevice-fromSiDevice');
35
+ expect(myMainStation1.multiplexerTarget).toBe(SiTargetMultiplexerTarget.Direct);
36
+ const myMainStation2 = SiMainStation.fromSiDevice(fakeSiDevice);
37
+ expect(myMainStation2).toBe(myMainStation1);
38
+ expect(myMainStation2.ident).toBe('Direct-FakeSiDevice-fromSiDevice');
39
+ expect(myMainStation2.multiplexerTarget).toBe(SiTargetMultiplexerTarget.Direct);
40
+ });
41
+ test('fromSiTargetMultiplexer', () => {
42
+ const myTargetMultiplexer = new SiTargetMultiplexer({ ident: 'fake-ident' } as ISiDevice<ISiDeviceDriverData<unknown>>);
43
+ const myMainStation1 = SiMainStation.fromSiTargetMultiplexer(myTargetMultiplexer);
44
+ expect(myMainStation1 instanceof SiMainStation).toBe(true);
45
+ expect(myMainStation1.ident).toBe('Direct-fake-ident');
46
+ expect(myMainStation1.multiplexerTarget).toBe(SiTargetMultiplexerTarget.Direct);
47
+ const myMainStation2 = SiMainStation.fromSiTargetMultiplexer(myTargetMultiplexer);
48
+ expect(myMainStation2).toBe(myMainStation1);
49
+ expect(myMainStation2.ident).toBe('Direct-fake-ident');
50
+ expect(myMainStation2.multiplexerTarget).toBe(SiTargetMultiplexerTarget.Direct);
51
+ });
52
+
53
+ test('can readCards', async () => {
54
+ const storage = siStationStorageDefinition(getBSM8Station().storageData);
55
+ storage.set('mode', 'Control');
56
+ storage.set('code', 31);
57
+ storage.set('autoSend', true);
58
+ storage.set('handshake', false);
59
+ storage.set('beeps', false);
60
+ storage.set('flashes', false);
61
+ const fakeSiTargetMultiplexer = {
62
+ addEventListener: () => undefined,
63
+ sendMessage: (_target: SiTargetMultiplexerTarget, message: siProtocol.SiMessage, _numResponses: number) => {
64
+ if (message.mode !== undefined) {
65
+ throw new Error();
66
+ }
67
+ if (message.command === proto.cmd.GET_SYS_VAL) {
68
+ return Promise.resolve([[0x00, 0x00, 0x00, ...storage.data]]);
69
+ }
70
+ if (message.command === proto.cmd.SET_SYS_VAL) {
71
+ storage.splice(message.parameters[0], message.parameters.length - 1, ...message.parameters.slice(1));
72
+ return Promise.resolve([[0x00, 0x00, message.parameters[0]]]);
73
+ }
74
+ throw new Error();
75
+ }
76
+ } as unknown as ISiTargetMultiplexer;
77
+ const mySiStation = new SiMainStation(fakeSiTargetMultiplexer);
78
+ const cardsRead: ISiCard[] = [];
79
+ const cleanUpFunction = await mySiStation.readCards((card) => {
80
+ cardsRead.push(card);
81
+ });
82
+ await testUtils.nTimesAsync(3, () => testUtils.advanceTimersByTime(0));
83
+ if (cleanUpFunction === undefined) {
84
+ throw new Error('expect cleanUp function');
85
+ }
86
+ expect(storage.get('mode')!.value).toBe('Readout');
87
+ expect(storage.get('code')!.value).toBe(10);
88
+ expect(storage.get('autoSend')!.value).toBe(false);
89
+ expect(storage.get('handshake')!.value).toBe(true);
90
+ expect(storage.get('beeps')!.value).toBe(true);
91
+ expect(storage.get('flashes')!.value).toBe(true);
92
+
93
+ const fakeSiCard = {
94
+ cardNumber: 1234
95
+ } as ISiCard;
96
+ mySiStation.dispatchEvent('siCardInserted', new SiMainStationSiCardInsertedEvent(mySiStation, fakeSiCard));
97
+ await testUtils.nTimesAsync(1, () => testUtils.advanceTimersByTime(0));
98
+ expect(cardsRead).toEqual([{ cardNumber: 1234 } as ISiCard]);
99
+
100
+ const actualCleanUpFunction: () => Promise<void> = cleanUpFunction;
101
+ await actualCleanUpFunction();
102
+ expect(storage.get('mode')!.value).toBe('Control');
103
+ expect(storage.get('code')!.value).toBe(31);
104
+ expect(storage.get('autoSend')!.value).toBe(true);
105
+ expect(storage.get('handshake')!.value).toBe(false);
106
+ expect(storage.get('beeps')!.value).toBe(false);
107
+ expect(storage.get('flashes')!.value).toBe(false);
108
+
109
+ mySiStation.dispatchEvent('siCardInserted', new SiMainStationSiCardInsertedEvent(mySiStation, fakeSiCard));
110
+ await testUtils.nTimesAsync(1, () => testUtils.advanceTimersByTime(0));
111
+ // No additional entry
112
+ expect(cardsRead).toEqual([{ cardNumber: 1234 } as ISiCard]);
113
+ });
114
+
115
+ test('card detection & removal', async () => {
116
+ const myTargetMultiplexer = new SiTargetMultiplexer({} as ISiDevice<ISiDeviceDriverData<unknown>>);
117
+ const myMainStation = SiMainStation.fromSiTargetMultiplexer(myTargetMultiplexer);
118
+ const testData = getSiCard5Examples().fullCard;
119
+ const myFakeSiCard5 = new FakeSiCard5(testData.storageData);
120
+
121
+ const insertedCardNumbers: number[] = [];
122
+ const handleCardInserted = (e: SiMainStationSiCardInsertedEvent) => {
123
+ insertedCardNumbers.push(e.siCard.cardNumber);
124
+ };
125
+ myMainStation.addEventListener('siCardInserted', handleCardInserted);
126
+ const message = myFakeSiCard5.handleDetect() as siProtocol.SiMessageWithoutMode;
127
+ myTargetMultiplexer.dispatchEvent(
128
+ 'message',
129
+ new SiTargetMultiplexerMessageEvent(myTargetMultiplexer, {
130
+ command: message.command,
131
+ parameters: [...[0x00, 0x00], ...message.parameters] as number[]
132
+ })
133
+ );
134
+ await testUtils.nTimesAsync(2, () => testUtils.advanceTimersByTime(0));
135
+ expect(insertedCardNumbers).toEqual([406402]);
136
+
137
+ const removedCardNumbers: number[] = [];
138
+ const handleCardRemoved = (e: SiMainStationSiCardRemovedEvent) => {
139
+ removedCardNumbers.push(e.siCard.cardNumber);
140
+ };
141
+ myMainStation.addEventListener('siCardRemoved', handleCardRemoved);
142
+ myTargetMultiplexer.dispatchEvent(
143
+ 'message',
144
+ new SiTargetMultiplexerMessageEvent(myTargetMultiplexer, {
145
+ command: proto.cmd.SI_REM,
146
+ parameters: [...[0x00, 0x00], ...message.parameters] as number[]
147
+ })
148
+ );
149
+ await testUtils.nTimesAsync(2, () => testUtils.advanceTimersByTime(0));
150
+ expect(removedCardNumbers).toEqual([406402]);
151
+
152
+ myTargetMultiplexer.dispatchEvent(
153
+ 'message',
154
+ new SiTargetMultiplexerMessageEvent(myTargetMultiplexer, {
155
+ command: proto.cmd.SI_REM,
156
+ parameters: [0x00, 0x00, 0x01, 0x23, 0x45, 0x67]
157
+ })
158
+ );
159
+ await testUtils.nTimesAsync(2, () => testUtils.advanceTimersByTime(0));
160
+ expect(removedCardNumbers).toEqual([406402]);
161
+
162
+ myMainStation.removeEventListener('siCardInserted', handleCardInserted);
163
+ myMainStation.removeEventListener('siCardRemoved', handleCardRemoved);
164
+ });
165
+ test('card observation', async () => {
166
+ const myTargetMultiplexer = new SiTargetMultiplexer({} as ISiDevice<ISiDeviceDriverData<unknown>>);
167
+ const myMainStation = SiMainStation.fromSiTargetMultiplexer(myTargetMultiplexer);
168
+ const testData = getSiCard6Examples().fullCard;
169
+ const myFakeSiCard6 = new FakeSiCard6(testData.storageData);
170
+ const observedCardNumbers: number[] = [];
171
+ const handleCardObserved = (e: SiMainStationSiCardObservedEvent) => {
172
+ observedCardNumbers.push(e.siCard.cardNumber);
173
+ };
174
+ myMainStation.addEventListener('siCardObserved', handleCardObserved);
175
+ const message = myFakeSiCard6.handleDetect() as siProtocol.SiMessageWithoutMode;
176
+ myTargetMultiplexer.dispatchEvent(
177
+ 'message',
178
+ new SiTargetMultiplexerMessageEvent(myTargetMultiplexer, {
179
+ command: proto.cmd.TRANS_REC,
180
+ parameters: [...[0x00, 0x00], ...message.parameters] as number[]
181
+ })
182
+ );
183
+
184
+ await testUtils.nTimesAsync(2, () => testUtils.advanceTimersByTime(0));
185
+ expect(observedCardNumbers).toEqual([testData.cardData.cardNumber]);
186
+ });
187
+ test('card observation with mode', async () => {
188
+ const myTargetMultiplexer = new SiTargetMultiplexer({} as ISiDevice<ISiDeviceDriverData<unknown>>);
189
+ const myMainStation = SiMainStation.fromSiTargetMultiplexer(myTargetMultiplexer);
190
+ const observedCardNumbers: number[] = [];
191
+ const handleCardObserved = (e: SiMainStationSiCardObservedEvent) => {
192
+ observedCardNumbers.push(e.siCard.cardNumber);
193
+ };
194
+ myMainStation.addEventListener('siCardObserved', handleCardObserved);
195
+ myTargetMultiplexer.dispatchEvent('message', new SiTargetMultiplexerMessageEvent(myTargetMultiplexer, { mode: proto.NAK }));
196
+
197
+ await testUtils.nTimesAsync(2, () => testUtils.advanceTimersByTime(0));
198
+ expect(observedCardNumbers).toEqual([]);
199
+ });
200
+ test('other message', () => {
201
+ const fakeSiTargetMultiplexer = {
202
+ addEventListener: () => undefined,
203
+ sendMessage: () => Promise.resolve([])
204
+ } as unknown as ISiTargetMultiplexer;
205
+ const mySiStation = new SiMainStation(fakeSiTargetMultiplexer);
206
+ mySiStation.handleMessage({ command: proto.cmd.SIGNAL, parameters: [0x01] });
207
+ });
208
+
209
+ test('sendMessage', async () => {
210
+ const fakeSiTargetMultiplexer = {
211
+ addEventListener: () => undefined,
212
+ sendMessage: () => Promise.resolve([])
213
+ } as unknown as ISiTargetMultiplexer;
214
+ const mySiStation = new SiMainStation(fakeSiTargetMultiplexer);
215
+ let sendMessageSucceeded:boolean|undefined = undefined;
216
+ mySiStation.sendMessage({} as siProtocol.SiMessage).then(() => {
217
+ sendMessageSucceeded = true;
218
+ });
219
+ await testUtils.nTimesAsync(1, () => testUtils.advanceTimersByTime(0));
220
+ expect(sendMessageSucceeded).toBe(true);
221
+ });
222
+ });
@@ -0,0 +1,133 @@
1
+ import * as utils from '../utils';
2
+ import * as siProtocol from '../siProtocol';
3
+ import { proto } from '../constants';
4
+ import { BaseSiCard } from '../SiCard';
5
+ import type { ISiCard } from '../SiCard/ISiCard';
6
+ import type { ISiDevice, ISiDeviceDriverData } from '../SiDevice/ISiDevice';
7
+ import type { ISiStation } from './ISiStation';
8
+ import { type SiMainStationEvents, SiMainStationSiCardInsertedEvent, SiMainStationSiCardObservedEvent, SiMainStationSiCardRemovedEvent } from './ISiMainStation';
9
+ import { type ISiTargetMultiplexer, SiTargetMultiplexerMessageEvent, SiTargetMultiplexerTarget } from './ISiTargetMultiplexer';
10
+ import { BaseSiStation, type ISiStationStorageFields } from './BaseSiStation';
11
+ import { SiTargetMultiplexer } from './SiTargetMultiplexer';
12
+
13
+ type SiStationSetup = {
14
+ [key in keyof ISiStationStorageFields]?: ISiStationStorageFields[key];
15
+ };
16
+
17
+ type CleanUpFunction = () => Promise<void>;
18
+
19
+ export class SiMainStation extends BaseSiStation<SiTargetMultiplexerTarget.Direct> implements ISiStation<SiTargetMultiplexerTarget.Direct> {
20
+ static fromSiDevice(siDevice: ISiDevice<ISiDeviceDriverData<unknown>>): SiMainStation {
21
+ const multiplexer = SiTargetMultiplexer.fromSiDevice(siDevice);
22
+ return this.fromSiTargetMultiplexer(multiplexer);
23
+ }
24
+
25
+ static fromSiTargetMultiplexer(multiplexer: ISiTargetMultiplexer): SiMainStation {
26
+ return this.fromSiTargetMultiplexerWithGivenTarget(multiplexer, SiTargetMultiplexerTarget.Direct, () => new this(multiplexer, SiTargetMultiplexerTarget.Direct)) as SiMainStation;
27
+ }
28
+
29
+ public siCard: ISiCard | null = null;
30
+
31
+ constructor(siTargetMultiplexer: ISiTargetMultiplexer, multiplexerTarget: SiTargetMultiplexerTarget.Direct = SiTargetMultiplexerTarget.Direct) {
32
+ super(siTargetMultiplexer, multiplexerTarget);
33
+ siTargetMultiplexer.addEventListener(
34
+ 'message', // not directMessage, as the target might still be unknown, but we still need the message
35
+ (e: SiTargetMultiplexerMessageEvent) => {
36
+ const message = e.message;
37
+ this.handleMessage(message);
38
+ console.log(`There's a SiMainStation listening to this ${message}`);
39
+ }
40
+ );
41
+ }
42
+
43
+ readCards(onCardInserted: (card: ISiCard) => void, siStationSetupModification: SiStationSetup = {}): Promise<CleanUpFunction> {
44
+ const siStationSetup: SiStationSetup = {
45
+ code: 10,
46
+ mode: 'Readout',
47
+ autoSend: false,
48
+ handshake: true,
49
+ beeps: true,
50
+ flashes: true,
51
+ ...siStationSetupModification
52
+ };
53
+ const siStationSetupKeys = Object.keys(siStationSetup) as (keyof ISiStationStorageFields)[];
54
+ const oldState: SiStationSetup = {};
55
+
56
+ const handleCardInserted = (e: SiMainStationSiCardInsertedEvent) => {
57
+ onCardInserted(e.siCard);
58
+ };
59
+
60
+ const cleanUp: CleanUpFunction = () => {
61
+ this.removeEventListener('siCardInserted', handleCardInserted);
62
+ return this.atomically(() => {
63
+ siStationSetupKeys.forEach((infoName) => {
64
+ const oldInfoValue = oldState[infoName];
65
+ if (oldInfoValue !== undefined) {
66
+ this.setInfo(infoName, oldInfoValue);
67
+ }
68
+ });
69
+ });
70
+ };
71
+
72
+ return this.atomically(() => {
73
+ siStationSetupKeys.forEach((infoName: keyof ISiStationStorageFields) => {
74
+ const oldFieldValue = this.getInfo(infoName);
75
+ // @ts-ignore
76
+ oldState[infoName] = oldFieldValue ? oldFieldValue.value : undefined;
77
+ const newValue = siStationSetup[infoName];
78
+ if (newValue !== undefined) {
79
+ this.setInfo(infoName, newValue);
80
+ }
81
+ });
82
+ }).then(() => {
83
+ this.addEventListener('siCardInserted', handleCardInserted);
84
+ return cleanUp;
85
+ });
86
+ }
87
+
88
+ handleMessage(message: siProtocol.SiMessage): void {
89
+ if (message.mode !== undefined) {
90
+ return;
91
+ }
92
+ const { command, parameters } = message;
93
+ const detectedSiCard = BaseSiCard.detectFromMessage(message);
94
+ if (detectedSiCard !== undefined) {
95
+ detectedSiCard.mainStation = this;
96
+ this.siCard = detectedSiCard as ISiCard;
97
+ this.dispatchEvent('siCardInserted', new SiMainStationSiCardInsertedEvent(this, detectedSiCard as ISiCard));
98
+ return;
99
+ }
100
+ const handleSiCardRemoved = () => {
101
+ const removedCardNumber = siProtocol.arr2cardNumber([parameters[5], parameters[4], parameters[3]]); // TODO: also [2]?
102
+ if (this.siCard !== null && this.siCard.cardNumber === removedCardNumber) {
103
+ this.dispatchEvent('siCardRemoved', new SiMainStationSiCardRemovedEvent(this, this.siCard));
104
+ } else {
105
+ console.warn(`Card ${removedCardNumber} was removed, but never inserted`);
106
+ }
107
+ this.siCard = null;
108
+ };
109
+ const handleSiCardObserved = () => {
110
+ const observedCardNumber = siProtocol.arr2cardNumber([parameters[5], parameters[4], parameters[3]]); // TODO: also [2]?
111
+ if (observedCardNumber === undefined) {
112
+ return;
113
+ }
114
+ const transRecordCard = BaseSiCard.fromCardNumber(observedCardNumber);
115
+ if (transRecordCard !== undefined) {
116
+ transRecordCard.mainStation = this;
117
+ this.dispatchEvent('siCardObserved', new SiMainStationSiCardObservedEvent(this, transRecordCard as ISiCard));
118
+ }
119
+ };
120
+ const handlerByCommand: { [command: number]: () => void } = {
121
+ [proto.cmd.SI_REM]: handleSiCardRemoved,
122
+ [proto.cmd.TRANS_REC]: handleSiCardObserved
123
+ };
124
+ const handler = handlerByCommand[command];
125
+ if (handler === undefined) {
126
+ return;
127
+ }
128
+ handler();
129
+ }
130
+ }
131
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
132
+ export interface SiMainStation extends utils.EventTarget<SiMainStationEvents> {}
133
+ utils.applyMixins(SiMainStation, [utils.EventTarget]);
@@ -0,0 +1,50 @@
1
+ import * as siProtocol from '../siProtocol';
2
+ import { SiSendTaskState } from './ISiSendTask';
3
+
4
+ export class SiSendTask {
5
+ public state: SiSendTaskState = SiSendTaskState.Queued;
6
+ public responses: number[][] = [];
7
+ private timeoutTimer: unknown;
8
+
9
+ constructor(
10
+ public message: siProtocol.SiMessage,
11
+ public numResponses: number,
12
+ public timeoutInMiliseconds: number,
13
+ public onResolve: (task: SiSendTask) => void,
14
+ public onReject: (task: SiSendTask) => void
15
+ ) {
16
+ this.timeoutTimer = setTimeout(() => {
17
+ const shouldAbortInState: { [state in SiSendTaskState]: boolean } = {
18
+ [SiSendTaskState.Queued]: true,
19
+ [SiSendTaskState.Sending]: true,
20
+ [SiSendTaskState.Sent]: true,
21
+ [SiSendTaskState.Succeeded]: false,
22
+ [SiSendTaskState.Failed]: false
23
+ };
24
+ if (!shouldAbortInState[this.state]) {
25
+ return;
26
+ }
27
+ console.debug(`Timeout: ${siProtocol.prettyMessage(this.message)} (expected ${this.numResponses} responses)`, this.responses);
28
+ this.fail();
29
+ }, timeoutInMiliseconds);
30
+ }
31
+
32
+ addResponse(response: number[]): void {
33
+ this.responses.push(response);
34
+ if (this.responses.length === this.numResponses) {
35
+ this.succeed();
36
+ }
37
+ }
38
+
39
+ succeed(): void {
40
+ this.state = SiSendTaskState.Succeeded;
41
+ clearTimeout(this.timeoutTimer as Parameters<typeof clearTimeout>[0]);
42
+ this.onResolve(this);
43
+ }
44
+
45
+ fail(): void {
46
+ this.state = SiSendTaskState.Failed;
47
+ clearTimeout(this.timeoutTimer as Parameters<typeof clearTimeout>[0]);
48
+ this.onReject(this);
49
+ }
50
+ }
@@ -0,0 +1,112 @@
1
+ import { describe, expect, test } from '@jest/globals';
2
+ import { proto } from '../constants';
3
+ import * as siProtocol from '../siProtocol';
4
+ import * as testUtils from '../testUtils';
5
+ import { SiDevice } from '../SiDevice/SiDevice';
6
+ import { SiDeviceState, SiDeviceReceiveEvent, type ISiDeviceDriverData } from '../SiDevice/ISiDevice';
7
+ import type { ISiDeviceDriver } from '../SiDevice/ISiDeviceDriver';
8
+ import { SiTargetMultiplexerTarget } from './ISiTargetMultiplexer';
9
+ import { SiTargetMultiplexer } from './SiTargetMultiplexer';
10
+
11
+ testUtils.useFakeTimers();
12
+
13
+ function mockDriver(driver: Partial<ISiDeviceDriver<ISiDeviceDriverData<unknown>>>) {
14
+ return driver as unknown as ISiDeviceDriver<ISiDeviceDriverData<unknown>>;
15
+ }
16
+
17
+ describe('SiTargetMultiplexer', () => {
18
+ test('handles targeting', async () => {
19
+ const siDevice = new SiDevice('handlesTargeting0', {
20
+ driver: mockDriver({
21
+ send: () => Promise.resolve()
22
+ })
23
+ });
24
+ siDevice.setState(SiDeviceState.Opened);
25
+ const muxer = SiTargetMultiplexer.fromSiDevice(siDevice);
26
+ expect(muxer instanceof SiTargetMultiplexer).toBe(true);
27
+
28
+ const randomMessage = testUtils.getRandomMessage({});
29
+ const timeState = {
30
+ sendingFinished: false,
31
+ resendingFinished: false,
32
+ remoteSendingFinished: false
33
+ };
34
+ muxer.sendMessage(SiTargetMultiplexerTarget.Direct, randomMessage, 0, 1).then((responses) => {
35
+ expect(responses.length).toBe(0);
36
+ expect(muxer._test.sendQueue.length).toBe(0);
37
+ timeState.sendingFinished = true;
38
+ });
39
+ setTimeout(() => {
40
+ siDevice.dispatchEvent(
41
+ 'receive',
42
+ new SiDeviceReceiveEvent(
43
+ siDevice,
44
+ siProtocol.render({
45
+ command: proto.cmd.SET_MS,
46
+ parameters: [0x00, 0x00, proto.P_MS_DIRECT]
47
+ })
48
+ )
49
+ );
50
+ }, 1);
51
+ expect(muxer.target).toBe(SiTargetMultiplexerTarget.Unknown);
52
+ expect(muxer._test.latestTarget).toBe(SiTargetMultiplexerTarget.Direct);
53
+ await testUtils.advanceTimersByTime(0); // resolve setTarget send promise
54
+ await testUtils.advanceTimersByTime(1); // trigger setTarget receive
55
+ await testUtils.nTimesAsync(10, () => testUtils.advanceTimersByTime(1));
56
+ expect(muxer.target).toBe(SiTargetMultiplexerTarget.Direct);
57
+ expect(muxer._test.latestTarget).toBe(SiTargetMultiplexerTarget.Direct);
58
+ await testUtils.advanceTimersByTime(0);
59
+ await testUtils.advanceTimersByTime(0);
60
+ expect(timeState).toEqual({
61
+ sendingFinished: true,
62
+ resendingFinished: false,
63
+ remoteSendingFinished: false
64
+ });
65
+
66
+ muxer.sendMessage(SiTargetMultiplexerTarget.Direct, randomMessage, 0, 1).then((responses) => {
67
+ expect(responses.length).toBe(0);
68
+ expect(muxer._test.sendQueue.length).toBe(0);
69
+ timeState.resendingFinished = true;
70
+ });
71
+ await testUtils.advanceTimersByTime(0);
72
+ await testUtils.advanceTimersByTime(0);
73
+ expect(muxer.target).toBe(SiTargetMultiplexerTarget.Direct);
74
+ expect(muxer._test.latestTarget).toBe(SiTargetMultiplexerTarget.Direct);
75
+ expect(timeState).toEqual({
76
+ sendingFinished: true,
77
+ resendingFinished: true,
78
+ remoteSendingFinished: false
79
+ });
80
+
81
+ muxer.sendMessage(SiTargetMultiplexerTarget.Remote, randomMessage, 0, 1).then((responses) => {
82
+ expect(responses.length).toBe(0);
83
+ expect(muxer._test.sendQueue.length).toBe(0);
84
+ timeState.remoteSendingFinished = true;
85
+ });
86
+ setTimeout(() => {
87
+ siDevice.dispatchEvent(
88
+ 'receive',
89
+ new SiDeviceReceiveEvent(
90
+ siDevice,
91
+ siProtocol.render({
92
+ command: proto.cmd.SET_MS,
93
+ parameters: [0x00, 0x00, proto.P_MS_REMOTE]
94
+ })
95
+ )
96
+ );
97
+ }, 1);
98
+ expect(muxer.target).toBe(SiTargetMultiplexerTarget.Direct);
99
+ expect(muxer._test.latestTarget).toBe(SiTargetMultiplexerTarget.Remote);
100
+ await testUtils.advanceTimersByTime(0); // resolve setTarget send promise
101
+ await testUtils.advanceTimersByTime(1); // trigger setTarget receive
102
+ expect(muxer.target).toBe(SiTargetMultiplexerTarget.Remote);
103
+ expect(muxer._test.latestTarget).toBe(SiTargetMultiplexerTarget.Remote);
104
+ await testUtils.advanceTimersByTime(0);
105
+ await testUtils.advanceTimersByTime(0);
106
+ expect(timeState).toEqual({
107
+ sendingFinished: true,
108
+ resendingFinished: true,
109
+ remoteSendingFinished: true
110
+ });
111
+ });
112
+ });