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,260 @@
1
+ import { describe, expect, test } from '@jest/globals';
2
+ import type { IRaceResultData } from './IRaceResultData';
3
+ import { getOrderedRaceResult, getRaceResultFromOrdered, type IOrderedRaceResult, makeStartZeroTime, monotonizeOrderedRaceResult, monotonizeRaceResult, prettyRaceResult } from './raceResultTools';
4
+
5
+ const EMPTY_RACE_RESULT: IRaceResultData = {};
6
+ const EMPTY_ORDERED_RACE_RESULT: IOrderedRaceResult = {
7
+ orderedTimes: []
8
+ };
9
+
10
+ const UNKNOWN_PUNCH_TIME_RACE_RESULT: IRaceResultData = {
11
+ punches: [{ code: 31, time: null }]
12
+ };
13
+ const UNKNOWN_PUNCH_TIME_ORDERED_RACE_RESULT: IOrderedRaceResult = {
14
+ orderedTimes: [],
15
+ punches: [{ code: 31, timeIndex: null }]
16
+ };
17
+
18
+ const WITHOUT_PUNCHES_RACE_RESULT: IRaceResultData = {
19
+ cardNumber: 1234,
20
+ cardHolder: { firstName: 'John' },
21
+ clearTime: {time:1},
22
+ checkTime: {time:2},
23
+ startTime: {time:3},
24
+ finishTime: {time:6}
25
+ };
26
+ const WITHOUT_PUNCHES_ORDERED_RACE_RESULT: IOrderedRaceResult = {
27
+ cardNumber: 1234,
28
+ cardHolder: { firstName: 'John' },
29
+ orderedTimes: [{time:1}, {time:2}, {time:3}, {time:6}],
30
+ clearTimeIndex: 0,
31
+ checkTimeIndex: 1,
32
+ startTimeIndex: 2,
33
+ finishTimeIndex: 3
34
+ };
35
+
36
+ const COMPLETE_RACE_RESULT: IRaceResultData = {
37
+ cardNumber: 1234,
38
+ cardHolder: { firstName: 'John' },
39
+ clearTime: {time:1},
40
+ checkTime: {time:2},
41
+ startTime: {time:3},
42
+ finishTime: {time:6},
43
+ punches: [
44
+ { code: 31, time: {time:4} },
45
+ { code: 32, time: {time:5} }
46
+ ]
47
+ };
48
+ const COMPLETE_ORDERED_RACE_RESULT: IOrderedRaceResult = {
49
+ cardNumber: 1234,
50
+ cardHolder: { firstName: 'John' },
51
+ orderedTimes: [{time:1}, {time:2}, {time:3}, {time:4}, {time:5}, {time:6}],
52
+ clearTimeIndex: 0,
53
+ checkTimeIndex: 1,
54
+ startTimeIndex: 2,
55
+ punches: [
56
+ { code: 31, timeIndex: 3 },
57
+ { code: 32, timeIndex: 4 }
58
+ ],
59
+ finishTimeIndex: 5
60
+ };
61
+
62
+ const UNKNOWN_TIMES_RACE_RESULT: IRaceResultData = {
63
+ checkTime: {time:2},
64
+ startTime: {time:3},
65
+ finishTime: {time:6},
66
+ punches: [
67
+ { code: 31, time: null },
68
+ { code: 32, time: {time:5} }
69
+ ]
70
+ };
71
+ const UNKNOWN_TIMES_ORDERED_RACE_RESULT: IOrderedRaceResult = {
72
+ orderedTimes: [{time:2}, {time:3}, {time:5}, {time:6}],
73
+ checkTimeIndex: 0,
74
+ startTimeIndex: 1,
75
+ punches: [
76
+ { code: 31, timeIndex: null },
77
+ { code: 32, timeIndex: 2 }
78
+ ],
79
+ finishTimeIndex: 3
80
+ };
81
+
82
+ const IMMONOTONE_RACE_RESULT: IRaceResultData = {
83
+ cardNumber: 1234,
84
+ cardHolder: { firstName: 'John' },
85
+ clearTime: {time:1},
86
+ checkTime: {time:2},
87
+ startTime: {time:1},
88
+ finishTime: {time:2},
89
+ punches: [
90
+ { code: 31, time: {time:2} },
91
+ { code: 32, time: {time:1} }
92
+ ]
93
+ };
94
+ const MONOTONE_RACE_RESULT: IRaceResultData = {
95
+ cardNumber: 1234,
96
+ cardHolder: { firstName: 'John' },
97
+ clearTime: {time:1},
98
+ checkTime: {time:2},
99
+ startTime: {time:43201},
100
+ finishTime: {time:86402},
101
+ punches: [
102
+ { code: 31, time: {time:43202} },
103
+ { code: 32, time: {time:86401} }
104
+ ]
105
+ };
106
+
107
+ const IMMONOTONE_ORDERED_RACE_RESULT: IOrderedRaceResult = {
108
+ cardNumber: 1234,
109
+ cardHolder: { firstName: 'John' },
110
+ orderedTimes: [{time:1}, {time:2}, {time:1}, {time:2}, {time:1}, {time:2}],
111
+ clearTimeIndex: 0,
112
+ checkTimeIndex: 1,
113
+ startTimeIndex: 2,
114
+ punches: [
115
+ { code: 31, timeIndex: 3 },
116
+ { code: 32, timeIndex: 4 }
117
+ ],
118
+ finishTimeIndex: 5
119
+ };
120
+ const MONOTONE_ORDERED_RACE_RESULT: IOrderedRaceResult = {
121
+ cardNumber: 1234,
122
+ cardHolder: { firstName: 'John' },
123
+ orderedTimes: [{time:1}, {time:2}, {time:43201}, {time:43202}, {time:86401}, {time:86402}],
124
+ clearTimeIndex: 0,
125
+ checkTimeIndex: 1,
126
+ startTimeIndex: 2,
127
+ punches: [
128
+ { code: 31, timeIndex: 3 },
129
+ { code: 32, timeIndex: 4 }
130
+ ],
131
+ finishTimeIndex: 5
132
+ };
133
+
134
+ const COMPLETE_START_ZEROED_RACE_RESULT: IRaceResultData = {
135
+ cardNumber: 1234,
136
+ cardHolder: { firstName: 'John' },
137
+ clearTime: {time:-2},
138
+ checkTime: {time:-1},
139
+ startTime: {time:0},
140
+ finishTime: {time:3},
141
+ punches: [
142
+ { code: 31, time: {time:1} },
143
+ { code: 32, time: {time:2} }
144
+ ]
145
+ };
146
+
147
+ const UNKNOWN_TIMES_START_ZEROED_RACE_RESULT: IRaceResultData = {
148
+ checkTime: {time:-1},
149
+ startTime: {time:0},
150
+ finishTime: {time:3},
151
+ punches: [
152
+ { code: 31, time: null },
153
+ { code: 32, time: {time:2} }
154
+ ]
155
+ };
156
+
157
+ const WITHOUT_PUNCHES_START_ZEROED_RACE_RESULT: IRaceResultData = {
158
+ cardNumber: 1234,
159
+ cardHolder: { firstName: 'John' },
160
+ clearTime: {time:-2},
161
+ checkTime: {time:-1},
162
+ startTime: {time:0},
163
+ finishTime: {time:3}
164
+ };
165
+
166
+ describe('raceResultTools', () => {
167
+ describe('prettyRaceResult', () => {
168
+ test('works for empty race result', async () => {
169
+ const emptyRaceResult: IRaceResultData = {};
170
+ expect(prettyRaceResult(emptyRaceResult)).toEqual('Card Number: ?\nClear: ?\nCheck: ?\nStart: ?\nFinish: ?\n? Punches\nCard Holder:\n?\n');
171
+ });
172
+ test('works for race result with zero punches', async () => {
173
+ const zeroPunchesRaceResult: IRaceResultData = {
174
+ punches: []
175
+ };
176
+ expect(prettyRaceResult(zeroPunchesRaceResult)).toEqual('Card Number: ?\nClear: ?\nCheck: ?\nStart: ?\nFinish: ?\nNo Punches\nCard Holder:\n?\n');
177
+ });
178
+ test('works for race result with empty card holder', async () => {
179
+ const emptyCardHolderRaceResult: IRaceResultData = {
180
+ cardHolder: {}
181
+ };
182
+ expect(prettyRaceResult(emptyCardHolderRaceResult)).toEqual('Card Number: ?\nClear: ?\nCheck: ?\nStart: ?\nFinish: ?\n? Punches\nCard Holder:\nEmpty Card Holder\n');
183
+ });
184
+ test('works for complete race result', async () => {
185
+ const completeRaceResult: IRaceResultData = {
186
+ cardNumber: 123,
187
+ cardHolder: { firstName: 'John' },
188
+ clearTime: {time:1},
189
+ checkTime: {time:2},
190
+ startTime: {time:3},
191
+ finishTime: {time:5},
192
+ punches: [{ code: 31, time: {time:4} }]
193
+ };
194
+ expect(prettyRaceResult(completeRaceResult)).toEqual('Card Number: 123\nClear: 1\nCheck: 2\nStart: 3\nFinish: 5\n31: 4\nCard Holder:\nfirstName: John\n');
195
+ });
196
+ });
197
+
198
+ describe('getOrderedRaceResult', () => {
199
+ test('works for empty race result', async () => {
200
+ expect(getOrderedRaceResult(EMPTY_RACE_RESULT)).toEqual(EMPTY_ORDERED_RACE_RESULT);
201
+ });
202
+ test('works for race result with punches of unknown time', async () => {
203
+ expect(getOrderedRaceResult(UNKNOWN_PUNCH_TIME_RACE_RESULT)).toEqual(UNKNOWN_PUNCH_TIME_ORDERED_RACE_RESULT);
204
+ });
205
+ test('works for complete race result', async () => {
206
+ expect(getOrderedRaceResult(COMPLETE_RACE_RESULT)).toEqual(COMPLETE_ORDERED_RACE_RESULT);
207
+ });
208
+ test('works for race result without punches', async () => {
209
+ expect(getOrderedRaceResult(WITHOUT_PUNCHES_RACE_RESULT)).toEqual(WITHOUT_PUNCHES_ORDERED_RACE_RESULT);
210
+ });
211
+ test('works for race result without clearTime and startTime', async () => {
212
+ expect(getOrderedRaceResult(UNKNOWN_TIMES_RACE_RESULT)).toEqual(UNKNOWN_TIMES_ORDERED_RACE_RESULT);
213
+ });
214
+ });
215
+
216
+ describe('getRaceResultFromOrdered', () => {
217
+ test('works for empty race result', async () => {
218
+ expect(getRaceResultFromOrdered(EMPTY_ORDERED_RACE_RESULT)).toEqual(EMPTY_RACE_RESULT);
219
+ });
220
+ test('works for race result with punches of unknown time', async () => {
221
+ expect(getRaceResultFromOrdered(UNKNOWN_PUNCH_TIME_ORDERED_RACE_RESULT)).toEqual(UNKNOWN_PUNCH_TIME_RACE_RESULT);
222
+ });
223
+ test('works for complete race result', async () => {
224
+ expect(getRaceResultFromOrdered(COMPLETE_ORDERED_RACE_RESULT)).toEqual(COMPLETE_RACE_RESULT);
225
+ });
226
+ test('works for race result without punches', async () => {
227
+ expect(getRaceResultFromOrdered(WITHOUT_PUNCHES_ORDERED_RACE_RESULT)).toEqual(WITHOUT_PUNCHES_RACE_RESULT);
228
+ });
229
+ test('works for race result without clearTime and punch[0]', async () => {
230
+ expect(getRaceResultFromOrdered(UNKNOWN_TIMES_ORDERED_RACE_RESULT)).toEqual(UNKNOWN_TIMES_RACE_RESULT);
231
+ });
232
+ });
233
+
234
+ describe('monotonizeOrderedRaceResult', () => {
235
+ test('works for ordered race result', async () => {
236
+ expect(monotonizeOrderedRaceResult(IMMONOTONE_ORDERED_RACE_RESULT)).toEqual(MONOTONE_ORDERED_RACE_RESULT);
237
+ });
238
+ });
239
+
240
+ describe('monotonizeRaceResult', () => {
241
+ test('works for race result', async () => {
242
+ expect(monotonizeRaceResult(IMMONOTONE_RACE_RESULT)).toEqual(MONOTONE_RACE_RESULT);
243
+ });
244
+ });
245
+
246
+ describe('makeStartZeroTime', () => {
247
+ test('works for complete race result', async () => {
248
+ expect(makeStartZeroTime(COMPLETE_RACE_RESULT)).toEqual(COMPLETE_START_ZEROED_RACE_RESULT);
249
+ });
250
+ test('works for race result with unknown times', async () => {
251
+ expect(makeStartZeroTime(UNKNOWN_TIMES_RACE_RESULT)).toEqual(UNKNOWN_TIMES_START_ZEROED_RACE_RESULT);
252
+ });
253
+ test('works for race result without punches', async () => {
254
+ expect(makeStartZeroTime(WITHOUT_PUNCHES_RACE_RESULT)).toEqual(WITHOUT_PUNCHES_START_ZEROED_RACE_RESULT);
255
+ });
256
+ test('fails for race result without start time', async () => {
257
+ expect(() => makeStartZeroTime(EMPTY_RACE_RESULT)).toThrow();
258
+ });
259
+ });
260
+ });
@@ -0,0 +1,150 @@
1
+ import * as siProtocol from '../siProtocol';
2
+ import type { IRaceResultData, IPunch } from './IRaceResultData';
3
+
4
+ export interface IOrderedRaceResult {
5
+ cardNumber?: number;
6
+ cardHolder?: { [property: string]: unknown };
7
+ orderedTimes: siProtocol.SiTimestamp[];
8
+ clearTimeIndex?: number | null;
9
+ checkTimeIndex?: number | null;
10
+ startTimeIndex?: number | null;
11
+ punches?: IOrderedPunch[];
12
+ finishTimeIndex?: number | null;
13
+ }
14
+
15
+ export interface IOrderedPunch {
16
+ code: number;
17
+ timeIndex: number | null;
18
+ }
19
+
20
+ export const prettyRaceResult = (result: IRaceResultData): string => {
21
+ const punchesString = result.punches ? (result.punches.length > 0 ? result.punches.map((punch) => `${punch.code}: ${punch.time?.time??"?"}`).join('\n') : 'No Punches') : '? Punches';
22
+ const cardHolderString = result.cardHolder
23
+ ? Object.keys(result.cardHolder).length > 0
24
+ ? Object.keys(result.cardHolder)
25
+ .map((key) => `${key}: ${result.cardHolder![key]}`)
26
+ .join('\n')
27
+ : 'Empty Card Holder'
28
+ : '?';
29
+ return (
30
+ `Card Number: ${result.cardNumber !== undefined ? result.cardNumber : '?'}\n` +
31
+ `Clear: ${result.clearTime !== undefined ? result.clearTime?.time : '?'}\n` +
32
+ `Check: ${result.checkTime !== undefined ? result.checkTime?.time : '?'}\n` +
33
+ `Start: ${result.startTime !== undefined ? result.startTime?.time : '?'}\n` +
34
+ `Finish: ${result.finishTime !== undefined ? result.finishTime?.time : '?'}\n` +
35
+ `${punchesString}\n` +
36
+ 'Card Holder:\n' +
37
+ `${cardHolderString}\n`
38
+ );
39
+ };
40
+
41
+ export const getOrderedRaceResult = (result: IRaceResultData): IOrderedRaceResult => {
42
+ const orderedResult: IOrderedRaceResult = {
43
+ cardNumber: result.cardNumber,
44
+ cardHolder: result.cardHolder,
45
+ orderedTimes: []
46
+ };
47
+ if (result.clearTime !== undefined && result.clearTime !== null) {
48
+ orderedResult.clearTimeIndex = orderedResult.orderedTimes.length;
49
+ orderedResult.orderedTimes.push(result.clearTime);
50
+ }
51
+ if (result.checkTime !== undefined && result.checkTime !== null) {
52
+ orderedResult.checkTimeIndex = orderedResult.orderedTimes.length;
53
+ orderedResult.orderedTimes.push(result.checkTime);
54
+ }
55
+ if (result.startTime !== undefined && result.startTime !== null) {
56
+ orderedResult.startTimeIndex = orderedResult.orderedTimes.length;
57
+ orderedResult.orderedTimes.push(result.startTime);
58
+ }
59
+ if (result.punches !== undefined) {
60
+ const newPunches: IOrderedPunch[] = [];
61
+ result.punches.forEach((punch: IPunch) => {
62
+ if (punch.time !== null) {
63
+ newPunches.push({
64
+ code: punch.code,
65
+ timeIndex: orderedResult.orderedTimes.length
66
+ });
67
+ orderedResult.orderedTimes.push(punch.time);
68
+ } else {
69
+ newPunches.push({
70
+ code: punch.code,
71
+ timeIndex: null
72
+ });
73
+ }
74
+ });
75
+ orderedResult.punches = newPunches;
76
+ }
77
+ if (result.finishTime !== undefined && result.finishTime !== null) {
78
+ orderedResult.finishTimeIndex = orderedResult.orderedTimes.length;
79
+ orderedResult.orderedTimes.push(result.finishTime);
80
+ }
81
+ return orderedResult;
82
+ };
83
+
84
+ export const getRaceResultFromOrdered = (orderedResult: IOrderedRaceResult): IRaceResultData => {
85
+ const getOrderedTimeIndexIfSet = (index: number | null) => (index === null ? null : orderedResult.orderedTimes[index]);
86
+ const getOrderedTimeIndexIfSetAndDefined = (index: number | null | undefined) => (index === undefined || index === null ? index : orderedResult.orderedTimes[index]);
87
+ return {
88
+ cardNumber: orderedResult.cardNumber,
89
+ cardHolder: orderedResult.cardHolder,
90
+ clearTime: getOrderedTimeIndexIfSetAndDefined(orderedResult.clearTimeIndex),
91
+ checkTime: getOrderedTimeIndexIfSetAndDefined(orderedResult.checkTimeIndex),
92
+ startTime: getOrderedTimeIndexIfSetAndDefined(orderedResult.startTimeIndex),
93
+ finishTime: getOrderedTimeIndexIfSetAndDefined(orderedResult.finishTimeIndex),
94
+ punches:
95
+ orderedResult.punches === undefined
96
+ ? undefined
97
+ : orderedResult.punches.map((punch: IOrderedPunch) => ({
98
+ code: punch.code,
99
+ time: getOrderedTimeIndexIfSet(punch.timeIndex)
100
+ }))
101
+ };
102
+ };
103
+
104
+ export const monotonizeOrderedRaceResult = (orderedData: IOrderedRaceResult): IOrderedRaceResult => {
105
+ let currentCarry = 0;
106
+ let lastTime = 0;
107
+ const newOrderedTimes = orderedData.orderedTimes.map((ts: siProtocol.SiTimestamp) => {
108
+ if(ts != null){
109
+ if (ts.time < lastTime) {
110
+ currentCarry += siProtocol.SI_TIME_CUTOFF;
111
+ }
112
+ lastTime = ts.time;
113
+ ts.time += currentCarry
114
+ }
115
+ return ts;
116
+ });
117
+ return {
118
+ ...orderedData,
119
+ orderedTimes: newOrderedTimes
120
+ };
121
+ };
122
+
123
+ export const monotonizeRaceResult = (result: IRaceResultData): IRaceResultData => {
124
+ const orderedResult = getOrderedRaceResult(result);
125
+ const monotonizedOrderedResult = monotonizeOrderedRaceResult(orderedResult);
126
+ return getRaceResultFromOrdered(monotonizedOrderedResult);
127
+ };
128
+
129
+ export const makeStartZeroTime = (result: IRaceResultData): IRaceResultData => {
130
+ const zeroTime = result.startTime;
131
+ if (zeroTime === undefined || zeroTime === null) {
132
+ throw new Error('start time must be known');
133
+ }
134
+ const deductZeroTimeIfSet = (time: siProtocol.SiTimestamp) => (time === null ? null : {...time, time: (time.time - zeroTime.time)});
135
+ const deductZeroTimeIfSetAndDefined = (time: siProtocol.SiTimestamp | undefined) => (time === null || time === undefined ? time : {...time, time: (time.time - zeroTime.time)});
136
+ return {
137
+ ...result,
138
+ clearTime: deductZeroTimeIfSetAndDefined(result.clearTime),
139
+ checkTime: deductZeroTimeIfSetAndDefined(result.checkTime),
140
+ startTime: deductZeroTimeIfSetAndDefined(result.startTime),
141
+ finishTime: deductZeroTimeIfSetAndDefined(result.finishTime),
142
+ punches:
143
+ result.punches === undefined
144
+ ? undefined
145
+ : result.punches.map((punch: IPunch) => ({
146
+ ...punch,
147
+ time: deductZeroTimeIfSet(punch.time)
148
+ }))
149
+ };
150
+ };
@@ -0,0 +1,19 @@
1
+ import { describe, expect, test } from '@jest/globals';
2
+ import { BaseSiCard } from '../BaseSiCard';
3
+ import { FCard } from './FCard';
4
+
5
+ describe('FCard', () => {
6
+ test('is registered', () => {
7
+ expect(BaseSiCard.getTypeByCardNumber(13999999)).not.toEqual(FCard);
8
+ expect(BaseSiCard.getTypeByCardNumber(14000000)).toEqual(FCard);
9
+ expect(BaseSiCard.getTypeByCardNumber(14999999)).toEqual(FCard);
10
+ expect(BaseSiCard.getTypeByCardNumber(15000000)).not.toEqual(FCard);
11
+ });
12
+ test('is not implemented', (done) => {
13
+ const fCard = new FCard(14000000);
14
+ fCard.read().then(
15
+ () => done(new Error('expect reject')),
16
+ () => done()
17
+ );
18
+ });
19
+ });
@@ -0,0 +1,14 @@
1
+ import * as utils from '../../utils';
2
+ import type * as siProtocol from '../../siProtocol';
3
+ import { BaseSiCard } from '../BaseSiCard';
4
+
5
+ export class FCard extends BaseSiCard {
6
+ static typeSpecificInstanceFromMessage(_message: siProtocol.SiMessage): FCard | undefined {
7
+ return undefined;
8
+ }
9
+
10
+ typeSpecificRead(): Promise<void> {
11
+ return Promise.reject(new utils.NotImplementedError());
12
+ }
13
+ }
14
+ BaseSiCard.registerNumberRange(14000000, 15000000, FCard);
@@ -0,0 +1,186 @@
1
+ import { describe, expect, test } from '@jest/globals';
2
+ import _ from 'lodash';
3
+ import * as utils from '../../utils';
4
+ import type * as siProtocol from '../../siProtocol';
5
+ import { cropPunches, getCroppedString, getPunchOffset, ModernSiCard, ModernSiCardSeries, parseCardHolder, parseCardHolderString } from './ModernSiCard';
6
+ import { getModernSiCardExamples } from './modernSiCardExamples';
7
+ import { FakeModernSiCard } from '../../fakes/FakeSiCard/types/FakeModernSiCard';
8
+
9
+ describe('ModernSiCard', () => {
10
+ test('exists', () => {
11
+ expect(ModernSiCard).not.toBe(undefined);
12
+
13
+ const myModernSiCard = new ModernSiCard(0);
14
+ expect(myModernSiCard.storage.data.has(0)).toBe(true);
15
+ expect(myModernSiCard.storage.data.get(0)).toBe(undefined);
16
+ expect(myModernSiCard.storage.data.has(1023)).toBe(true);
17
+ expect(myModernSiCard.storage.data.get(1023)).toBe(undefined);
18
+ expect(myModernSiCard.storage.data.has(1024)).toBe(false);
19
+ expect(myModernSiCard.storage.data.get(1024)).toBe(undefined);
20
+ });
21
+ test('getPunchOffset', () => {
22
+ expect(getPunchOffset(0)).toEqual(0x200);
23
+ expect(getPunchOffset(1)).toEqual(0x204);
24
+ expect(getPunchOffset(64)).toEqual(0x300);
25
+ expect(getPunchOffset(127)).toEqual(0x3fc);
26
+ });
27
+ test('cropPunches', () => {
28
+ expect(cropPunches([])).toEqual([]);
29
+ expect(cropPunches([{ code: 31, time: {time:1} }])).toEqual([{ code: 31, time: {time:1} }]);
30
+ expect(cropPunches([{ code: 32, time: null }])).toEqual([]);
31
+ expect(cropPunches([{ code: undefined, time: undefined }])).toEqual([]);
32
+ expect(cropPunches([{ code: 33, time: undefined }])).toEqual([]);
33
+ expect(
34
+ cropPunches([
35
+ { code: 31, time: {time:1} },
36
+ { code: 32, time: {time:2} },
37
+ { code: 33, time: {time:3} },
38
+ { code: 0xee, time: null },
39
+ { code: undefined, time: undefined }
40
+ ])
41
+ ).toEqual([
42
+ { code: 31, time: {time:1} },
43
+ { code: 32, time: {time:2} },
44
+ { code: 33, time: {time:3} }
45
+ ]);
46
+ });
47
+ const cardHolderCharCodes1 = utils.unPrettyHex(`
48
+ 4A 6F 68 6E 3B 44 6F 65 3B 6D 3B 31 39 39 30 30
49
+ 31 33 31 3B 45 78 61 6D 70 6C 65 63 6C 75 62 3B
50
+ 6A 6F 68 6E 2E 64 6F 65 40 67 6D 61 69 6C 2E 63
51
+ 6F 6D 3B 2B 30 31 32 33 34 35 36 37 38 39 3B 45
52
+ 78 61 6D 70 6C 65 74 6F 6E 3B 53 61 6D 70 6C 65
53
+ 20 41 6C 6C 65 79 20 31 32 33 3B 31 32 33 34 3B
54
+ 45 58 50 3B EE EE EE EE EE EE EE EE EE EE EE EE
55
+ EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE
56
+ `);
57
+ const cardHolderString1 = 'John;Doe;m;19900131;Exampleclub;john.doe@gmail.com;' + '+0123456789;Exampleton;Sample Alley 123;1234;EXP;';
58
+ const emptyCardHolderDict = {
59
+ firstName: undefined,
60
+ lastName: undefined,
61
+ gender: undefined,
62
+ birthday: undefined,
63
+ club: undefined,
64
+ email: undefined,
65
+ phone: undefined,
66
+ city: undefined,
67
+ street: undefined,
68
+ zip: undefined,
69
+ country: undefined,
70
+ isComplete: false
71
+ };
72
+ const cardHolderDict1 = {
73
+ firstName: 'John',
74
+ lastName: 'Doe',
75
+ gender: 'm',
76
+ birthday: '19900131',
77
+ club: 'Exampleclub',
78
+ email: 'john.doe@gmail.com',
79
+ phone: '+0123456789',
80
+ city: 'Exampleton',
81
+ street: 'Sample Alley 123',
82
+ zip: '1234',
83
+ country: 'EXP',
84
+ isComplete: true
85
+ };
86
+ test('getCroppedString', () => {
87
+ expect(getCroppedString([])).toEqual('');
88
+ expect(getCroppedString([0x61])).toEqual('a');
89
+ expect(getCroppedString([0xee])).toEqual('');
90
+ expect(getCroppedString([0x41, 0xee])).toEqual('A');
91
+ expect(getCroppedString(cardHolderCharCodes1)).toEqual(cardHolderString1);
92
+ });
93
+ test('parseCardHolderString', () => {
94
+ expect(parseCardHolderString('')).toEqual(emptyCardHolderDict);
95
+ expect(parseCardHolderString('A')).toEqual(emptyCardHolderDict);
96
+ expect(parseCardHolderString('A;')).toEqual({ ...emptyCardHolderDict, firstName: 'A' });
97
+ expect(parseCardHolderString('A;B')).toEqual({ ...emptyCardHolderDict, firstName: 'A' });
98
+ expect(parseCardHolderString(cardHolderString1)).toEqual(cardHolderDict1);
99
+ });
100
+ test('parseCardHolder', () => {
101
+ expect(parseCardHolder([])).toEqual(emptyCardHolderDict);
102
+ expect(parseCardHolder([0x61])).toEqual(emptyCardHolderDict);
103
+ expect(parseCardHolder([0x61, 0x3b])).toEqual({ ...emptyCardHolderDict, firstName: 'a' });
104
+ expect(parseCardHolder([0xee])).toEqual(emptyCardHolderDict);
105
+ expect(parseCardHolder([0x41, 0xee, 0x3b])).toEqual(emptyCardHolderDict);
106
+ expect(parseCardHolder([0x41, 0x3b, 0xee])).toEqual({ ...emptyCardHolderDict, firstName: 'A' });
107
+ expect(parseCardHolder(cardHolderCharCodes1)).toEqual(cardHolderDict1);
108
+ });
109
+ test('typeSpecificRead fails without mainStation', (done) => {
110
+ const myModernSiCard = new ModernSiCard(1);
111
+ myModernSiCard.typeSpecificRead().then(
112
+ () => done(new Error('expect reject')),
113
+ () => done()
114
+ );
115
+ });
116
+ const examples = getModernSiCardExamples();
117
+ Object.keys(examples).forEach((exampleName) => {
118
+ test(`typeSpecificRead works with ${exampleName} example`, (done) => {
119
+ const { storageData, cardData } = examples[exampleName];
120
+ const myModernSiCard = new ModernSiCard(cardData.cardNumber);
121
+ const myFakeModernSiCard = new FakeModernSiCard(storageData);
122
+ myModernSiCard.mainStation = {
123
+ sendMessage: (message: siProtocol.SiMessage, numResponses?: number) => {
124
+ const responses: siProtocol.SiMessage[] = myFakeModernSiCard.handleRequest(message);
125
+ if (responses.length !== numResponses) {
126
+ throw new Error('Invalid numResponses');
127
+ }
128
+ return Promise.resolve(responses.map((response: siProtocol.SiMessage) => (response.mode === undefined ? [0x00, 0x00, ...response.parameters] : [])));
129
+ }
130
+ };
131
+
132
+ myModernSiCard.typeSpecificRead().then(() => {
133
+ expect(myModernSiCard.raceResult.cardNumber).toEqual(cardData.cardNumber);
134
+ expect(myModernSiCard.raceResult.startTime).toEqual(cardData.startTime);
135
+ expect(myModernSiCard.raceResult.finishTime).toEqual(cardData.finishTime);
136
+ expect(myModernSiCard.raceResult.checkTime).toEqual(cardData.checkTime);
137
+ expect(myModernSiCard.raceResult.punches).toEqual(cardData.punches);
138
+ expect(myModernSiCard.raceResult.cardHolder).toEqual(cardData.cardHolder);
139
+ expect(myModernSiCard.punchCount).toEqual(cardData.punchCount);
140
+
141
+ const cardSeriesString = myModernSiCard.storage.get('cardSeries')!.toString();
142
+ expect(cardSeriesString in ModernSiCardSeries).toBe(true);
143
+ done();
144
+ });
145
+ });
146
+ });
147
+ test('typeSpecificRead if typeSpecificReadCardHolder fails', (done) => {
148
+ const testError = new Error('test');
149
+ let typeSpecificReadCardHolderCalled = false;
150
+ class ModernSiCardWithoutCardHolder extends ModernSiCard {
151
+ typeSpecificGetPage() {
152
+ return Promise.resolve(_.range(128).map(() => 0x00));
153
+ }
154
+
155
+ typeSpecificReadCardHolder() {
156
+ typeSpecificReadCardHolderCalled = true;
157
+ return Promise.reject(testError);
158
+ }
159
+ }
160
+ const myModernSiCard = new ModernSiCardWithoutCardHolder(7123456);
161
+ myModernSiCard.typeSpecificRead().catch((err) => {
162
+ expect(typeSpecificReadCardHolderCalled).toBe(true);
163
+ expect(err).toBe(testError);
164
+ done();
165
+ });
166
+ });
167
+ test('typeSpecificRead if typeSpecificReadPunches fails', (done) => {
168
+ const testError = new Error('test');
169
+ let attemptedToGetPage4 = false;
170
+ class ModernSiCardWithoutCardHolder extends ModernSiCard {
171
+ typeSpecificGetPage(pageNumber: number) {
172
+ if (pageNumber === 4) {
173
+ attemptedToGetPage4 = true;
174
+ return Promise.reject(testError);
175
+ }
176
+ return Promise.resolve(_.range(128).map(() => 0x01));
177
+ }
178
+ }
179
+ const myModernSiCard = new ModernSiCardWithoutCardHolder(7123456);
180
+ myModernSiCard.typeSpecificRead().catch((err) => {
181
+ expect(attemptedToGetPage4).toBe(true);
182
+ expect(err).toBe(testError);
183
+ done();
184
+ });
185
+ });
186
+ });