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,137 @@
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 { BaseSiCard } from '../BaseSiCard';
7
+ import { ModernSiCardSeries } from './ModernSiCard';
8
+ import { getPunchOffset, SiCard8 } from './SiCard8';
9
+ import { getSiCard8Examples } from './siCard8Examples';
10
+ import { FakeSiCard8 } from '../../fakes/FakeSiCard/types/FakeSiCard8';
11
+
12
+ describe('SiCard8', () => {
13
+ test('is registered', () => {
14
+ expect(BaseSiCard.getTypeByCardNumber(1999999)).not.toEqual(SiCard8);
15
+ expect(BaseSiCard.getTypeByCardNumber(2000000)).toEqual(SiCard8);
16
+ expect(BaseSiCard.getTypeByCardNumber(2002999)).toEqual(SiCard8);
17
+ expect(BaseSiCard.getTypeByCardNumber(2003000)).not.toEqual(SiCard8);
18
+ expect(BaseSiCard.getTypeByCardNumber(2003999)).not.toEqual(SiCard8);
19
+ expect(BaseSiCard.getTypeByCardNumber(2004000)).toEqual(SiCard8);
20
+ expect(BaseSiCard.getTypeByCardNumber(2999999)).toEqual(SiCard8);
21
+ expect(BaseSiCard.getTypeByCardNumber(3000000)).not.toEqual(SiCard8);
22
+ });
23
+ describe('typeSpecificInstanceFromMessage', () => {
24
+ test('works for valid message', () => {
25
+ const instance = SiCard8.typeSpecificInstanceFromMessage({
26
+ command: proto.cmd.SI8_DET,
27
+ parameters: [0x00, 0x00, ModernSiCardSeries.SiCard8, 0x22, 0x22, 0x22]
28
+ });
29
+ if (instance === undefined) {
30
+ throw new Error('expect instance');
31
+ }
32
+ expect(instance instanceof SiCard8).toBe(true);
33
+ expect(instance.cardNumber).toBe(2236962);
34
+ });
35
+ test('returns undefined when message has mode', () => {
36
+ expect(
37
+ SiCard8.typeSpecificInstanceFromMessage({
38
+ mode: proto.NAK
39
+ })
40
+ ).toBe(undefined);
41
+ });
42
+ test('returns undefined when message has wrong command', () => {
43
+ expect(
44
+ SiCard8.typeSpecificInstanceFromMessage({
45
+ command: testUtils.getRandomByteExcept([proto.cmd.SI8_DET]),
46
+ parameters: []
47
+ })
48
+ ).toBe(undefined);
49
+ });
50
+ test('returns undefined when there are too few parameters', () => {
51
+ expect(
52
+ SiCard8.typeSpecificInstanceFromMessage({
53
+ command: proto.cmd.SI8_DET,
54
+ parameters: []
55
+ })
56
+ ).toBe(undefined);
57
+ });
58
+ test('returns undefined when the series does not match', () => {
59
+ expect(
60
+ SiCard8.typeSpecificInstanceFromMessage({
61
+ command: proto.cmd.SI8_DET,
62
+ parameters: [0x00, 0x00, testUtils.getRandomByteExcept([ModernSiCardSeries.SiCard8]), 0x22, 0x22, 0x22]
63
+ })
64
+ ).toBe(undefined);
65
+ });
66
+ });
67
+ test('getPunchOffset', () => {
68
+ expect(getPunchOffset(0)).toEqual(0x88);
69
+ expect(getPunchOffset(1)).toEqual(0x8c);
70
+ expect(getPunchOffset(29)).toEqual(0xfc);
71
+ });
72
+ const examples = getSiCard8Examples();
73
+ Object.keys(examples).forEach((exampleName) => {
74
+ const { storageData, cardData } = examples[exampleName];
75
+ const myFakeSiCard8 = new FakeSiCard8(storageData);
76
+ const mainStationSimulation = {
77
+ sendMessage: (message: siProtocol.SiMessage, numResponses?: number) => {
78
+ const responses: siProtocol.SiMessage[] = myFakeSiCard8.handleRequest(message);
79
+ if (responses.length !== numResponses) {
80
+ throw new Error('Invalid numResponses');
81
+ }
82
+ return Promise.resolve(responses.map((response: siProtocol.SiMessage) => (response.mode === undefined ? [0x00, 0x00, ...response.parameters] : [])));
83
+ }
84
+ };
85
+
86
+ test(`typeSpecificRead works with ${exampleName} example`, (done) => {
87
+ const mySiCard8 = new SiCard8(cardData.cardNumber);
88
+ mySiCard8.mainStation = mainStationSimulation;
89
+ mySiCard8.typeSpecificRead().then(() => {
90
+ expect(mySiCard8.raceResult.cardNumber).toEqual(cardData.cardNumber);
91
+ expect(mySiCard8.raceResult.startTime).toEqual(cardData.startTime);
92
+ expect(mySiCard8.raceResult.finishTime).toEqual(cardData.finishTime);
93
+ expect(mySiCard8.raceResult.checkTime).toEqual(cardData.checkTime);
94
+ expect(mySiCard8.raceResult.punches).toEqual(cardData.punches);
95
+ expect(mySiCard8.raceResult.cardHolder).toEqual(cardData.cardHolder);
96
+ expect(mySiCard8.punchCount).toEqual(cardData.punchCount);
97
+ expect(mySiCard8.uid).toEqual(cardData.uid);
98
+
99
+ const cardSeriesString = mySiCard8.storage.get('cardSeries')!.toString();
100
+ expect(cardSeriesString in ModernSiCardSeries).toBe(true);
101
+ done();
102
+ });
103
+ });
104
+
105
+ test(`typeSpecificRead works with wrong card number in ${exampleName} example`, (done) => {
106
+ const mySiCard8 = new SiCard8(cardData.cardNumber + 1);
107
+ mySiCard8.mainStation = mainStationSimulation;
108
+ mySiCard8.typeSpecificRead().then(() => {
109
+ expect(mySiCard8.raceResult.cardNumber).toEqual(cardData.cardNumber);
110
+ expect(mySiCard8.raceResult.startTime).toEqual(cardData.startTime);
111
+ expect(mySiCard8.raceResult.finishTime).toEqual(cardData.finishTime);
112
+ expect(mySiCard8.raceResult.checkTime).toEqual(cardData.checkTime);
113
+ expect(mySiCard8.raceResult.punches).toEqual(cardData.punches);
114
+ expect(mySiCard8.raceResult.cardHolder).toEqual(cardData.cardHolder);
115
+ expect(mySiCard8.punchCount).toEqual(cardData.punchCount);
116
+ expect(mySiCard8.uid).toEqual(cardData.uid);
117
+ done();
118
+ });
119
+ });
120
+ });
121
+ test('typeSpecificRead if typeSpecificGetPage fails', (done) => {
122
+ const testError = new Error('test');
123
+ let attemptedToGetPage = false;
124
+ class ModernSiCardWithoutCardHolder extends SiCard8 {
125
+ typeSpecificGetPage() {
126
+ attemptedToGetPage = true;
127
+ return Promise.reject(testError);
128
+ }
129
+ }
130
+ const myModernSiCard = new ModernSiCardWithoutCardHolder(7123456);
131
+ myModernSiCard.typeSpecificRead().catch((err) => {
132
+ expect(attemptedToGetPage).toBe(true);
133
+ expect(err).toBe(testError);
134
+ done();
135
+ });
136
+ });
137
+ });
@@ -0,0 +1,129 @@
1
+ import _ from 'lodash';
2
+ import * as storage from '../../storage';
3
+ import * as siProtocol from '../../siProtocol';
4
+ import { cropPunches, getCroppedString, ModernSiCard, ModernSiCardSeries } from './ModernSiCard';
5
+ import type { IBaseSiCardStorageFields } from '../ISiCard';
6
+ import { BaseSiCard } from '../BaseSiCard';
7
+
8
+ class ReadFinishedException {}
9
+ const punchesPerPage = 32;
10
+ const bytesPerPage = 128;
11
+ const MAX_NUM_PUNCHES = 30;
12
+
13
+ export const getPunchOffset = (i: number): number => 0x88 + i * 4;
14
+
15
+ const parseCardHolderString = (semicolonSeparatedString: string): { [property: string]: unknown } => {
16
+ const informationComponents = semicolonSeparatedString.split(';');
17
+ return {
18
+ firstName: informationComponents.length > 1 ? informationComponents[0] : undefined,
19
+ lastName: informationComponents.length > 2 ? informationComponents[1] : undefined,
20
+ isComplete: informationComponents.length > 2
21
+ };
22
+ };
23
+
24
+ const parseCardHolder = (maybeCharCodes: (number | undefined)[]) => {
25
+ const semicolonSeparatedString = getCroppedString(maybeCharCodes);
26
+ return parseCardHolderString(semicolonSeparatedString || '');
27
+ };
28
+
29
+ export interface ISiCard8StorageFields extends IBaseSiCardStorageFields {
30
+ uid: number;
31
+ cardSeries: keyof typeof ModernSiCardSeries;
32
+ }
33
+
34
+ export const siCard8StorageLocations: storage.ISiStorageLocations<ISiCard8StorageFields> = {
35
+ uid: new storage.SiInt([[0x03], [0x02], [0x01], [0x00]]),
36
+ cardSeries: new storage.SiEnum([[0x18]], ModernSiCardSeries),
37
+ cardNumber: new storage.SiModified(
38
+ new storage.SiArray(3, (i) => new storage.SiInt([[0x19 + (2 - i)]])),
39
+ (extractedValue) => siProtocol.arr2cardNumber(extractedValue)
40
+ // (cardNumber) => siProtocol.cardNumber2arr(cardNumber),
41
+ // (cardNumber) => `${cardNumber}`,
42
+ // (cardNumberString) => parseInt(cardNumberString, 10),
43
+ // (cardNumber) => cardNumber !== undefined && _.isInteger(cardNumber) && cardNumber >= 0,
44
+ ),
45
+ startTime: new siProtocol.SiTime([[0x0f], [0x0e], [0x0c]]),
46
+ finishTime: new siProtocol.SiTime([[0x13], [0x12], [0x10]]),
47
+ checkTime: new siProtocol.SiTime([[0x0b], [0x0a], [0x08]]),
48
+ punchCount: new storage.SiInt([[0x16]]),
49
+ punches: new storage.SiModified(
50
+ new storage.SiArray(
51
+ MAX_NUM_PUNCHES,
52
+ (i) =>
53
+ new storage.SiDict({
54
+ code: new storage.SiInt([[getPunchOffset(i) + 1]]),
55
+ time: new siProtocol.SiTime([[getPunchOffset(i) + 3], [getPunchOffset(i) + 2], [getPunchOffset(i) + 0]])
56
+ })
57
+ ),
58
+ (allPunches) => cropPunches(allPunches)
59
+ ),
60
+ cardHolder: new storage.SiModified(new storage.SiArray(0x60, (i) => new storage.SiInt([[0x20 + i]])), (charCodes) => parseCardHolder(charCodes))
61
+ };
62
+ export const siCard8StorageDefinition = storage.defineStorage(0x100, siCard8StorageLocations);
63
+
64
+ export class SiCard8 extends ModernSiCard {
65
+ static maxNumPunches = MAX_NUM_PUNCHES;
66
+
67
+ static typeSpecificInstanceFromMessage(message: siProtocol.SiMessage): SiCard8 | undefined {
68
+ const info = this.parseModernSiCardDetectionMessage(message);
69
+ if (info === undefined) {
70
+ return undefined;
71
+ }
72
+ if (info.cardSeries !== 'SiCard8') {
73
+ return undefined;
74
+ }
75
+ return new this(info.cardNumber);
76
+ }
77
+
78
+ public storage: storage.ISiStorage<ISiCard8StorageFields>;
79
+
80
+ public uid?: number;
81
+
82
+ constructor(cardNumber: number) {
83
+ super(cardNumber);
84
+ this.storage = siCard8StorageDefinition();
85
+ }
86
+
87
+ typeSpecificRead(): Promise<void> {
88
+ return new Promise((resolve, reject) => {
89
+ this.typeSpecificGetPage(0)
90
+ .then((page0: number[]) => {
91
+ this.storage.splice(bytesPerPage * 0, bytesPerPage, ...page0);
92
+
93
+ const readCardNumber = this.storage.get('cardNumber')!.value;
94
+ if (this.cardNumber !== readCardNumber) {
95
+ console.warn(`SICard8 Number ${readCardNumber} (expected ${this.cardNumber})`);
96
+ }
97
+
98
+ if (this.storage.get('punchCount')!.value <= punchesPerPage * 0) {
99
+ throw new ReadFinishedException();
100
+ }
101
+ return this.typeSpecificGetPage(1);
102
+ })
103
+ .then((page1: number[]) => {
104
+ this.storage.splice(bytesPerPage * 1, bytesPerPage, ...page1);
105
+ throw new ReadFinishedException();
106
+ })
107
+ .catch((exc: Error) => {
108
+ if (exc instanceof ReadFinishedException) {
109
+ this.raceResult = {
110
+ cardNumber: this.storage.get('cardNumber')!.value,
111
+ startTime: this.storage.get('startTime')?.value,
112
+ finishTime: this.storage.get('finishTime')?.value,
113
+ checkTime: this.storage.get('checkTime')?.value,
114
+ punches: this.storage.get('punches')!.value,
115
+ cardHolder: this.storage.get('cardHolder')!.value
116
+ };
117
+ this.punchCount = this.storage.get('punchCount')!.value;
118
+ this.cardSeries = this.storage.get('cardSeries')!.value;
119
+ this.uid = this.storage.get('uid')!.value;
120
+ resolve();
121
+ } else {
122
+ reject(exc);
123
+ }
124
+ });
125
+ });
126
+ }
127
+ }
128
+ BaseSiCard.registerNumberRange(2000000, 2003000, SiCard8);
129
+ BaseSiCard.registerNumberRange(2004000, 3000000, SiCard8);
@@ -0,0 +1,132 @@
1
+ import { describe, expect, test } from '@jest/globals';
2
+ import { proto } from '../../constants';
3
+ import type * as siProtocol from '../../siProtocol';
4
+ import * as testUtils from '../../testUtils';
5
+ import { BaseSiCard } from '../BaseSiCard';
6
+ import { ModernSiCardSeries } from './ModernSiCard';
7
+ import { getPunchOffset, SiCard9 } from './SiCard9';
8
+ import { getSiCard9Examples } from './siCard9Examples';
9
+ import { FakeSiCard9 } from '../../fakes/FakeSiCard/types/FakeSiCard9';
10
+
11
+ describe('SiCard9', () => {
12
+ test('is registered', () => {
13
+ expect(BaseSiCard.getTypeByCardNumber(999999)).not.toEqual(SiCard9);
14
+ expect(BaseSiCard.getTypeByCardNumber(1000000)).toEqual(SiCard9);
15
+ expect(BaseSiCard.getTypeByCardNumber(1999999)).toEqual(SiCard9);
16
+ expect(BaseSiCard.getTypeByCardNumber(2000000)).not.toEqual(SiCard9);
17
+ });
18
+ describe('typeSpecificInstanceFromMessage', () => {
19
+ test('works for valid message', () => {
20
+ const instance = SiCard9.typeSpecificInstanceFromMessage({
21
+ command: proto.cmd.SI8_DET,
22
+ parameters: [0x00, 0x00, ModernSiCardSeries.SiCard9, 0x11, 0x11, 0x11]
23
+ });
24
+ if (instance === undefined) {
25
+ throw new Error('expect instance');
26
+ }
27
+ expect(instance instanceof SiCard9).toBe(true);
28
+ expect(instance.cardNumber).toBe(1118481);
29
+ });
30
+ test('returns undefined when message has mode', () => {
31
+ expect(
32
+ SiCard9.typeSpecificInstanceFromMessage({
33
+ mode: proto.NAK
34
+ })
35
+ ).toBe(undefined);
36
+ });
37
+ test('returns undefined when message has wrong command', () => {
38
+ expect(
39
+ SiCard9.typeSpecificInstanceFromMessage({
40
+ command: testUtils.getRandomByteExcept([proto.cmd.SI8_DET]),
41
+ parameters: []
42
+ })
43
+ ).toBe(undefined);
44
+ });
45
+ test('returns undefined when there are too few parameters', () => {
46
+ expect(
47
+ SiCard9.typeSpecificInstanceFromMessage({
48
+ command: proto.cmd.SI8_DET,
49
+ parameters: []
50
+ })
51
+ ).toBe(undefined);
52
+ });
53
+ test('returns undefined when the series does not match', () => {
54
+ expect(
55
+ SiCard9.typeSpecificInstanceFromMessage({
56
+ command: proto.cmd.SI8_DET,
57
+ parameters: [0x00, 0x00, testUtils.getRandomByteExcept([ModernSiCardSeries.SiCard9]), 0x22, 0x22, 0x22]
58
+ })
59
+ ).toBe(undefined);
60
+ });
61
+ });
62
+ test('getPunchOffset', () => {
63
+ expect(getPunchOffset(0)).toEqual(0x38);
64
+ expect(getPunchOffset(1)).toEqual(0x3c);
65
+ expect(getPunchOffset(49)).toEqual(0xfc);
66
+ });
67
+ const examples = getSiCard9Examples();
68
+ Object.keys(examples).forEach((exampleName) => {
69
+ const { storageData, cardData } = examples[exampleName];
70
+ const myFakeSiCard9 = new FakeSiCard9(storageData);
71
+ const mainStationSimulation = {
72
+ sendMessage: (message: siProtocol.SiMessage, numResponses?: number) => {
73
+ const responses: siProtocol.SiMessage[] = myFakeSiCard9.handleRequest(message);
74
+ if (responses.length !== numResponses) {
75
+ throw new Error('Invalid numResponses');
76
+ }
77
+ return Promise.resolve(responses.map((response: siProtocol.SiMessage) => (response.mode === undefined ? [0x00, 0x00, ...response.parameters] : [])));
78
+ }
79
+ };
80
+
81
+ test(`typeSpecificRead works with ${exampleName} example`, (done) => {
82
+ const mySiCard9 = new SiCard9(cardData.cardNumber);
83
+ mySiCard9.mainStation = mainStationSimulation;
84
+ mySiCard9.typeSpecificRead().then(() => {
85
+ expect(mySiCard9.raceResult.cardNumber).toEqual(cardData.cardNumber);
86
+ expect(mySiCard9.raceResult.startTime).toEqual(cardData.startTime);
87
+ expect(mySiCard9.raceResult.finishTime).toEqual(cardData.finishTime);
88
+ expect(mySiCard9.raceResult.checkTime).toEqual(cardData.checkTime);
89
+ expect(mySiCard9.raceResult.punches).toEqual(cardData.punches);
90
+ expect(mySiCard9.raceResult.cardHolder).toEqual(cardData.cardHolder);
91
+ expect(mySiCard9.punchCount).toEqual(cardData.punchCount);
92
+ expect(mySiCard9.uid).toEqual(cardData.uid);
93
+
94
+ const cardSeriesString = mySiCard9.storage.get('cardSeries')!.toString();
95
+ expect(cardSeriesString in ModernSiCardSeries).toBe(true);
96
+ done();
97
+ });
98
+ });
99
+
100
+ test(`typeSpecificRead works with wrong card number in ${exampleName} example`, (done) => {
101
+ const mySiCard9 = new SiCard9(cardData.cardNumber + 1);
102
+ mySiCard9.mainStation = mainStationSimulation;
103
+ mySiCard9.typeSpecificRead().then(() => {
104
+ expect(mySiCard9.raceResult.cardNumber).toEqual(cardData.cardNumber);
105
+ expect(mySiCard9.raceResult.startTime).toEqual(cardData.startTime);
106
+ expect(mySiCard9.raceResult.finishTime).toEqual(cardData.finishTime);
107
+ expect(mySiCard9.raceResult.checkTime).toEqual(cardData.checkTime);
108
+ expect(mySiCard9.raceResult.punches).toEqual(cardData.punches);
109
+ expect(mySiCard9.raceResult.cardHolder).toEqual(cardData.cardHolder);
110
+ expect(mySiCard9.punchCount).toEqual(cardData.punchCount);
111
+ expect(mySiCard9.uid).toEqual(cardData.uid);
112
+ done();
113
+ });
114
+ });
115
+ });
116
+ test('typeSpecificRead if typeSpecificGetPage fails', (done) => {
117
+ const testError = new Error('test');
118
+ let attemptedToGetPage = false;
119
+ class ModernSiCardWithoutCardHolder extends SiCard9 {
120
+ typeSpecificGetPage() {
121
+ attemptedToGetPage = true;
122
+ return Promise.reject(testError);
123
+ }
124
+ }
125
+ const myModernSiCard = new ModernSiCardWithoutCardHolder(7123456);
126
+ myModernSiCard.typeSpecificRead().catch((err) => {
127
+ expect(attemptedToGetPage).toBe(true);
128
+ expect(err).toBe(testError);
129
+ done();
130
+ });
131
+ });
132
+ });
@@ -0,0 +1,128 @@
1
+ import _ from 'lodash';
2
+ import * as storage from '../../storage';
3
+ import * as siProtocol from '../../siProtocol';
4
+ import { cropPunches, getCroppedString, ModernSiCard, ModernSiCardSeries } from './ModernSiCard';
5
+ import type { IBaseSiCardStorageFields } from '../ISiCard';
6
+ import { BaseSiCard } from '../BaseSiCard';
7
+
8
+ class ReadFinishedException {}
9
+ const punchesPerPage = 32;
10
+ const bytesPerPage = 128;
11
+ const MAX_NUM_PUNCHES = 50;
12
+
13
+ export const getPunchOffset = (i: number): number => 0x38 + i * 4;
14
+
15
+ const parseCardHolderString = (semicolonSeparatedString: string): { [property: string]: unknown } => {
16
+ const informationComponents = semicolonSeparatedString.split(';');
17
+ return {
18
+ firstName: informationComponents.length > 1 ? informationComponents[0] : undefined,
19
+ lastName: informationComponents.length > 2 ? informationComponents[1] : undefined,
20
+ isComplete: informationComponents.length > 2
21
+ };
22
+ };
23
+
24
+ const parseCardHolder = (maybeCharCodes: (number | undefined)[]) => {
25
+ const semicolonSeparatedString = getCroppedString(maybeCharCodes);
26
+ return parseCardHolderString(semicolonSeparatedString || '');
27
+ };
28
+
29
+ export interface ISiCard9StorageFields extends IBaseSiCardStorageFields {
30
+ uid: number;
31
+ cardSeries: keyof typeof ModernSiCardSeries;
32
+ }
33
+
34
+ export const siCard9StorageLocations: storage.ISiStorageLocations<ISiCard9StorageFields> = {
35
+ uid: new storage.SiInt([[0x03], [0x02], [0x01], [0x00]]),
36
+ cardSeries: new storage.SiEnum([[0x18]], ModernSiCardSeries),
37
+ cardNumber: new storage.SiModified(
38
+ new storage.SiArray(3, (i) => new storage.SiInt([[0x19 + (2 - i)]])),
39
+ (extractedValue) => siProtocol.arr2cardNumber(extractedValue)
40
+ // (cardNumber) => siProtocol.cardNumber2arr(cardNumber),
41
+ // (cardNumber) => `${cardNumber}`,
42
+ // (cardNumberString) => parseInt(cardNumberString, 10),
43
+ // (cardNumber) => cardNumber !== undefined && _.isInteger(cardNumber) && cardNumber >= 0,
44
+ ),
45
+ startTime: new siProtocol.SiTime([[0x0f], [0x0e], [0x0c]]),
46
+ finishTime: new siProtocol.SiTime([[0x13], [0x12], [0x10]]),
47
+ checkTime: new siProtocol.SiTime([[0x0b], [0x0a], [0x08]]),
48
+ punchCount: new storage.SiInt([[0x16]]),
49
+ punches: new storage.SiModified(
50
+ new storage.SiArray(
51
+ MAX_NUM_PUNCHES,
52
+ (i) =>
53
+ new storage.SiDict({
54
+ code: new storage.SiInt([[getPunchOffset(i) + 1]]),
55
+ time: new siProtocol.SiTime([[getPunchOffset(i) + 3], [getPunchOffset(i) + 2], [getPunchOffset(i) + 0]])
56
+ })
57
+ ),
58
+ (allPunches) => cropPunches(allPunches)
59
+ ),
60
+ cardHolder: new storage.SiModified(new storage.SiArray(0x18, (i) => new storage.SiInt([[0x20 + i]])), (charCodes) => parseCardHolder(charCodes))
61
+ };
62
+ export const siCard9StorageDefinition = storage.defineStorage(0x100, siCard9StorageLocations);
63
+
64
+ export class SiCard9 extends ModernSiCard {
65
+ static maxNumPunches = MAX_NUM_PUNCHES;
66
+
67
+ static typeSpecificInstanceFromMessage(message: siProtocol.SiMessage): SiCard9 | undefined {
68
+ const info = this.parseModernSiCardDetectionMessage(message);
69
+ if (info === undefined) {
70
+ return undefined;
71
+ }
72
+ if (info.cardSeries !== 'SiCard9') {
73
+ return undefined;
74
+ }
75
+ return new this(info.cardNumber);
76
+ }
77
+
78
+ public storage: storage.ISiStorage<ISiCard9StorageFields>;
79
+
80
+ public uid?: number;
81
+
82
+ constructor(cardNumber: number) {
83
+ super(cardNumber);
84
+ this.storage = siCard9StorageDefinition();
85
+ }
86
+
87
+ typeSpecificRead(): Promise<void> {
88
+ return new Promise((resolve, reject) => {
89
+ this.typeSpecificGetPage(0)
90
+ .then((page0: number[]) => {
91
+ this.storage.splice(bytesPerPage * 0, bytesPerPage, ...page0);
92
+
93
+ const readCardNumber = this.storage.get('cardNumber')!.value;
94
+ if (this.cardNumber !== readCardNumber) {
95
+ console.warn(`SICard9 Number ${readCardNumber} (expected ${this.cardNumber})`);
96
+ }
97
+
98
+ if (this.storage.get('punchCount')!.value <= punchesPerPage * 0) {
99
+ throw new ReadFinishedException();
100
+ }
101
+ return this.typeSpecificGetPage(1);
102
+ })
103
+ .then((page1: number[]) => {
104
+ this.storage.splice(bytesPerPage * 1, bytesPerPage, ...page1);
105
+ throw new ReadFinishedException();
106
+ })
107
+ .catch((exc: Error) => {
108
+ if (exc instanceof ReadFinishedException) {
109
+ this.raceResult = {
110
+ cardNumber: this.storage.get('cardNumber')!.value,
111
+ startTime: this.storage.get('startTime')?.value,
112
+ finishTime: this.storage.get('finishTime')?.value,
113
+ checkTime: this.storage.get('checkTime')?.value,
114
+ punches: this.storage.get('punches')!.value,
115
+ cardHolder: this.storage.get('cardHolder')!.value
116
+ };
117
+ this.punchCount = this.storage.get('punchCount')!.value;
118
+ this.cardSeries = this.storage.get('cardSeries')!.value;
119
+ this.uid = this.storage.get('uid')!.value;
120
+ resolve();
121
+ } else {
122
+ reject(exc);
123
+ }
124
+ });
125
+ });
126
+ }
127
+ }
128
+ BaseSiCard.registerNumberRange(1000000, 2000000, SiCard9);
@@ -0,0 +1,19 @@
1
+ import { describe, expect, test } from '@jest/globals';
2
+ import { BaseSiCard } from '../BaseSiCard';
3
+ import { TCard } from './TCard';
4
+
5
+ describe('TCard', () => {
6
+ test('is registered', () => {
7
+ expect(BaseSiCard.getTypeByCardNumber(5999999)).not.toEqual(TCard);
8
+ expect(BaseSiCard.getTypeByCardNumber(6000000)).toEqual(TCard);
9
+ expect(BaseSiCard.getTypeByCardNumber(6999999)).toEqual(TCard);
10
+ expect(BaseSiCard.getTypeByCardNumber(7000000)).not.toEqual(TCard);
11
+ });
12
+ test('is not implemented', (done) => {
13
+ const tCard = new TCard(6000000);
14
+ tCard.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 TCard extends BaseSiCard {
6
+ static typeSpecificInstanceFromMessage(_message: siProtocol.SiMessage): TCard | undefined {
7
+ return undefined;
8
+ }
9
+
10
+ typeSpecificRead(): Promise<void> {
11
+ return Promise.reject(new utils.NotImplementedError());
12
+ }
13
+ }
14
+ BaseSiCard.registerNumberRange(6000000, 7000000, TCard);
@@ -0,0 +1,26 @@
1
+ import { describe, expect, test } from '@jest/globals';
2
+ import _ from 'lodash';
3
+ import * as utils from '../../utils';
4
+ import type { SiCardSample } from '../ISiCardExamples';
5
+ import { BaseSiCard, type SiCardType } from '../BaseSiCard';
6
+ import * as siCardIndex from './index';
7
+
8
+ describe('SiCard index', () => {
9
+ const cardTypesInRegistry = BaseSiCard.cardNumberRangeRegistry.values;
10
+ utils.typedKeys(siCardIndex).forEach((siCardExportName) => {
11
+ const siCardExport = siCardIndex[siCardExportName];
12
+ if (siCardExport.prototype instanceof BaseSiCard) {
13
+ const cardType = siCardExport as SiCardType<BaseSiCard>;
14
+ test(`card type ${siCardExportName} has been registered`, () => {
15
+ expect(cardTypesInRegistry.includes(cardType)).toBe(true);
16
+ });
17
+ } else if (/^get\S+Examples$/.exec(siCardExportName)) {
18
+ const getExamples = siCardExport as () => { [name: string]: SiCardSample };
19
+ test(`card type examples ${siCardExportName} can be retrieved`, () => {
20
+ expect(_.isPlainObject(getExamples())).toBe(true);
21
+ });
22
+ } else {
23
+ throw new Error('There are currently no other exports allowed');
24
+ }
25
+ });
26
+ });
@@ -0,0 +1,15 @@
1
+ export { FCard } from './FCard';
2
+ export { PCard } from './PCard';
3
+ export { SIAC } from './SIAC';
4
+ export { SiCard5 } from './SiCard5';
5
+ export { SiCard6 } from './SiCard6';
6
+ export { SiCard8 } from './SiCard8';
7
+ export { SiCard9 } from './SiCard9';
8
+ export { SiCard10 } from './SiCard10';
9
+ export { SiCard11 } from './SiCard11';
10
+ export { TCard } from './TCard';
11
+ export { getSiCard5Examples } from './siCard5Examples';
12
+ export { getSiCard6Examples } from './siCard6Examples';
13
+ export { getSiCard8Examples } from './siCard8Examples';
14
+ export { getSiCard9Examples } from './siCard9Examples';
15
+ export { getModernSiCardExamples } from './modernSiCardExamples';