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.
- package/.dependency-cruiser.js +233 -0
- package/.editorconfig +12 -0
- package/.eslintignore +6 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
- package/.github/workflows/npm.yml +17 -0
- package/LICENSE +21 -0
- package/README.md +31 -0
- package/babel.config.js +21 -0
- package/build-docs.sh +25 -0
- package/docs/index.md +9 -0
- package/docs/typedoc/index.md +11 -0
- package/eslint.base.js +232 -0
- package/install.sh +6 -0
- package/jest.config.ts +49 -0
- package/nx.json +39 -0
- package/package.json +51 -0
- package/src/SiCard/BaseSiCard.test.ts +187 -0
- package/src/SiCard/BaseSiCard.ts +101 -0
- package/src/SiCard/IRaceResultData.ts +16 -0
- package/src/SiCard/ISiCard.ts +23 -0
- package/src/SiCard/ISiCardExamples.ts +4 -0
- package/src/SiCard/index.ts +2 -0
- package/src/SiCard/raceResultTools.test.ts +260 -0
- package/src/SiCard/raceResultTools.ts +150 -0
- package/src/SiCard/types/FCard.test.ts +19 -0
- package/src/SiCard/types/FCard.ts +14 -0
- package/src/SiCard/types/ModernSiCard.test.ts +186 -0
- package/src/SiCard/types/ModernSiCard.ts +241 -0
- package/src/SiCard/types/PCard.test.ts +19 -0
- package/src/SiCard/types/PCard.ts +14 -0
- package/src/SiCard/types/SIAC.test.ts +84 -0
- package/src/SiCard/types/SIAC.ts +19 -0
- package/src/SiCard/types/SiCard10.test.ts +85 -0
- package/src/SiCard/types/SiCard10.ts +17 -0
- package/src/SiCard/types/SiCard11.test.ts +84 -0
- package/src/SiCard/types/SiCard11.ts +19 -0
- package/src/SiCard/types/SiCard5.test.ts +149 -0
- package/src/SiCard/types/SiCard5.ts +129 -0
- package/src/SiCard/types/SiCard6.test.ts +179 -0
- package/src/SiCard/types/SiCard6.ts +222 -0
- package/src/SiCard/types/SiCard8.test.ts +137 -0
- package/src/SiCard/types/SiCard8.ts +129 -0
- package/src/SiCard/types/SiCard9.test.ts +132 -0
- package/src/SiCard/types/SiCard9.ts +128 -0
- package/src/SiCard/types/TCard.test.ts +19 -0
- package/src/SiCard/types/TCard.ts +14 -0
- package/src/SiCard/types/index.test.ts +26 -0
- package/src/SiCard/types/index.ts +15 -0
- package/src/SiCard/types/modernSiCardExamples.ts +364 -0
- package/src/SiCard/types/siCard5Examples.ts +73 -0
- package/src/SiCard/types/siCard6Examples.ts +262 -0
- package/src/SiCard/types/siCard8Examples.ts +152 -0
- package/src/SiCard/types/siCard9Examples.ts +143 -0
- package/src/SiDevice/INavigatorWebSerial.ts +78 -0
- package/src/SiDevice/INavigatorWebUsb.ts +62 -0
- package/src/SiDevice/ISiDevice.ts +48 -0
- package/src/SiDevice/ISiDeviceDriver.ts +35 -0
- package/src/SiDevice/README.md +13 -0
- package/src/SiDevice/SiDevice.test.ts +354 -0
- package/src/SiDevice/SiDevice.ts +132 -0
- package/src/SiDevice/WebSerialSiDeviceDriver.ts +146 -0
- package/src/SiDevice/WebUsbSiDeviceDriver.ts +343 -0
- package/src/SiDevice/index.ts +3 -0
- package/src/SiDevice/testUtils/index.ts +2 -0
- package/src/SiDevice/testUtils/testISiDeviceDriver.ts +63 -0
- package/src/SiDevice/testUtils/testISiDeviceDriverWithAutodetection.ts +72 -0
- package/src/SiStation/BaseSiStation.test.ts +221 -0
- package/src/SiStation/BaseSiStation.ts +253 -0
- package/src/SiStation/CoupledSiStation.test.ts +41 -0
- package/src/SiStation/CoupledSiStation.ts +130 -0
- package/src/SiStation/ISiMainStation.ts +29 -0
- package/src/SiStation/ISiSendTask.ts +9 -0
- package/src/SiStation/ISiStation.ts +88 -0
- package/src/SiStation/ISiTargetMultiplexer.ts +51 -0
- package/src/SiStation/SiMainStation.test.ts +222 -0
- package/src/SiStation/SiMainStation.ts +133 -0
- package/src/SiStation/SiSendTask.ts +50 -0
- package/src/SiStation/SiTargetMultiplexer.targeting.test.ts +112 -0
- package/src/SiStation/SiTargetMultiplexer.test.ts +605 -0
- package/src/SiStation/SiTargetMultiplexer.ts +241 -0
- package/src/SiStation/index.ts +5 -0
- package/src/SiStation/siStationExamples.ts +103 -0
- package/src/constants.test.ts +17 -0
- package/src/constants.ts +92 -0
- package/src/fakes/FakeSiCard/BaseFakeSiCard.test.ts +11 -0
- package/src/fakes/FakeSiCard/BaseFakeSiCard.ts +10 -0
- package/src/fakes/FakeSiCard/IFakeSiCard.ts +6 -0
- package/src/fakes/FakeSiCard/index.ts +2 -0
- package/src/fakes/FakeSiCard/types/FakeModernSiCard.test.ts +62 -0
- package/src/fakes/FakeSiCard/types/FakeModernSiCard.ts +43 -0
- package/src/fakes/FakeSiCard/types/FakeSIAC.ts +17 -0
- package/src/fakes/FakeSiCard/types/FakeSiCard10.ts +17 -0
- package/src/fakes/FakeSiCard/types/FakeSiCard11.ts +17 -0
- package/src/fakes/FakeSiCard/types/FakeSiCard5.test.ts +42 -0
- package/src/fakes/FakeSiCard/types/FakeSiCard5.ts +40 -0
- package/src/fakes/FakeSiCard/types/FakeSiCard6.test.ts +62 -0
- package/src/fakes/FakeSiCard/types/FakeSiCard6.ts +44 -0
- package/src/fakes/FakeSiCard/types/FakeSiCard8.ts +16 -0
- package/src/fakes/FakeSiCard/types/FakeSiCard9.ts +16 -0
- package/src/fakes/FakeSiCard/types/index.ts +7 -0
- package/src/fakes/FakeSiDeviceDriver.ts +148 -0
- package/src/fakes/FakeSiMainStation.test.ts +141 -0
- package/src/fakes/FakeSiMainStation.ts +118 -0
- package/src/fakes/IFakeSiMainStation.ts +15 -0
- package/src/fakes/index.ts +2 -0
- package/src/index.ts +24 -0
- package/src/siProtocol.test.ts +509 -0
- package/src/siProtocol.ts +417 -0
- package/src/storage/SiArray.test.ts +103 -0
- package/src/storage/SiArray.ts +56 -0
- package/src/storage/SiBool.test.ts +81 -0
- package/src/storage/SiBool.ts +47 -0
- package/src/storage/SiDataType.test.ts +81 -0
- package/src/storage/SiDataType.ts +60 -0
- package/src/storage/SiDict.test.ts +115 -0
- package/src/storage/SiDict.ts +60 -0
- package/src/storage/SiEnum.test.ts +77 -0
- package/src/storage/SiEnum.ts +48 -0
- package/src/storage/SiFieldValue.test.ts +58 -0
- package/src/storage/SiFieldValue.ts +23 -0
- package/src/storage/SiInt.test.ts +80 -0
- package/src/storage/SiInt.ts +84 -0
- package/src/storage/SiModified.test.ts +135 -0
- package/src/storage/SiModified.ts +59 -0
- package/src/storage/SiStorage.test.ts +51 -0
- package/src/storage/SiStorage.ts +44 -0
- package/src/storage/index.test.ts +222 -0
- package/src/storage/index.ts +12 -0
- package/src/storage/interfaces.ts +41 -0
- package/src/storage/siStringEncoding.ts +1361 -0
- package/src/testUtils.test.ts +266 -0
- package/src/testUtils.ts +75 -0
- package/src/utils/NumberRange.test.ts +66 -0
- package/src/utils/NumberRange.ts +46 -0
- package/src/utils/NumberRangeRegistry.test.ts +49 -0
- package/src/utils/NumberRangeRegistry.ts +43 -0
- package/src/utils/bytes.test.ts +126 -0
- package/src/utils/bytes.ts +69 -0
- package/src/utils/errors.test.ts +29 -0
- package/src/utils/errors.ts +20 -0
- package/src/utils/events.test.ts +112 -0
- package/src/utils/events.ts +68 -0
- package/src/utils/general.test.ts +139 -0
- package/src/utils/general.ts +69 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/mixins.test.ts +40 -0
- package/src/utils/mixins.ts +13 -0
- package/src/utils/typed.ts +3 -0
- package/tsconfig.base.json +22 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import { proto } from '../../constants';
|
|
3
|
+
import * as storage from '../../storage';
|
|
4
|
+
import * as utils from '../../utils';
|
|
5
|
+
import * as siProtocol from '../../siProtocol';
|
|
6
|
+
import type { IBaseSiCardStorageFields } from '../ISiCard';
|
|
7
|
+
import { BaseSiCard } from '../BaseSiCard';
|
|
8
|
+
import type { IPunch } from '../IRaceResultData';
|
|
9
|
+
|
|
10
|
+
class ReadFinishedException {}
|
|
11
|
+
const punchesPerPage = 32;
|
|
12
|
+
const bytesPerPage = 128;
|
|
13
|
+
|
|
14
|
+
const MAX_NUM_PUNCHES = 128;
|
|
15
|
+
|
|
16
|
+
export const ModernSiCardSeries = {
|
|
17
|
+
SiCard8: 0x02,
|
|
18
|
+
SiCard9: 0x01,
|
|
19
|
+
SiCard10: 0x0f,
|
|
20
|
+
PCard: 0x04,
|
|
21
|
+
TCard: 0x06
|
|
22
|
+
// TODO: Find out these values
|
|
23
|
+
// SiCard11: ?,
|
|
24
|
+
// SIAC: ?,
|
|
25
|
+
// FCard: ?,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export interface PotentialModernSiCardPunch {
|
|
29
|
+
code: number | undefined;
|
|
30
|
+
time: siProtocol.SiTimestamp | undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const getPunchOffset = (i: number): number => bytesPerPage * 4 + i * 4;
|
|
34
|
+
|
|
35
|
+
export const cropPunches = (allPunches: (PotentialModernSiCardPunch | undefined)[]): IPunch[] => {
|
|
36
|
+
const isPunchEntryValid = (punch: PotentialModernSiCardPunch | undefined): punch is IPunch => punch !== undefined && punch.code !== undefined && punch.time !== undefined && punch.time !== null;
|
|
37
|
+
const firstInvalidIndex = allPunches.findIndex((punch) => !isPunchEntryValid(punch));
|
|
38
|
+
const punchesUntilInvalid = firstInvalidIndex === -1 ? allPunches : allPunches.slice(0, firstInvalidIndex);
|
|
39
|
+
return punchesUntilInvalid.filter<IPunch>(isPunchEntryValid);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const getCroppedString = (charCodes: (number | undefined)[]): string => {
|
|
43
|
+
const isCharacterInvalid = (charCode: number | undefined) => charCode === undefined || charCode === 0xee;
|
|
44
|
+
const firstInvalidIndex = charCodes.findIndex(isCharacterInvalid);
|
|
45
|
+
const croppedCharCodes = (firstInvalidIndex === -1 ? charCodes : charCodes.slice(0, firstInvalidIndex)) as number[];
|
|
46
|
+
return croppedCharCodes.map((charCode: number) => storage.siStringToUtf8(charCode)).join('');
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const parseCardHolderString = (semicolonSeparatedString: string): { [property: string]: unknown } => {
|
|
50
|
+
const informationComponents = semicolonSeparatedString.split(';');
|
|
51
|
+
return {
|
|
52
|
+
firstName: informationComponents.length > 1 ? informationComponents[0] : undefined,
|
|
53
|
+
lastName: informationComponents.length > 2 ? informationComponents[1] : undefined,
|
|
54
|
+
gender: informationComponents.length > 3 ? informationComponents[2] : undefined,
|
|
55
|
+
birthday: informationComponents.length > 4 ? informationComponents[3] : undefined,
|
|
56
|
+
club: informationComponents.length > 5 ? informationComponents[4] : undefined,
|
|
57
|
+
email: informationComponents.length > 6 ? informationComponents[5] : undefined,
|
|
58
|
+
phone: informationComponents.length > 7 ? informationComponents[6] : undefined,
|
|
59
|
+
city: informationComponents.length > 8 ? informationComponents[7] : undefined,
|
|
60
|
+
street: informationComponents.length > 9 ? informationComponents[8] : undefined,
|
|
61
|
+
zip: informationComponents.length > 10 ? informationComponents[9] : undefined,
|
|
62
|
+
country: informationComponents.length > 11 ? informationComponents[10] : undefined,
|
|
63
|
+
isComplete: informationComponents.length > 11
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const parseCardHolder = (maybeCharCodes: (number | undefined)[]): { [property: string]: unknown } => {
|
|
68
|
+
const semicolonSeparatedString = getCroppedString(maybeCharCodes);
|
|
69
|
+
return parseCardHolderString(semicolonSeparatedString || '');
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export interface IModernSiCardStorageFields extends IBaseSiCardStorageFields {
|
|
73
|
+
uid: number;
|
|
74
|
+
cardSeries: keyof typeof ModernSiCardSeries;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const modernSiCardStorageLocations: storage.ISiStorageLocations<IModernSiCardStorageFields> = {
|
|
78
|
+
uid: new storage.SiInt([[0x03], [0x02], [0x01], [0x00]]),
|
|
79
|
+
cardSeries: new storage.SiEnum([[0x18]], ModernSiCardSeries),
|
|
80
|
+
cardNumber: new storage.SiModified(
|
|
81
|
+
new storage.SiArray(3, (i) => new storage.SiInt([[0x19 + (2 - i)]])),
|
|
82
|
+
(extractedValue) => siProtocol.arr2cardNumber(extractedValue)
|
|
83
|
+
// (cardNumber) => siProtocol.cardNumber2arr(cardNumber),
|
|
84
|
+
// (cardNumber) => `${cardNumber}`,
|
|
85
|
+
// (cardNumberString) => parseInt(cardNumberString, 10),
|
|
86
|
+
// (cardNumber) => cardNumber !== undefined && _.isInteger(cardNumber) && cardNumber >= 0,
|
|
87
|
+
),
|
|
88
|
+
startTime: new siProtocol.SiTime([[0x0f], [0x0e], [0x0c]]),
|
|
89
|
+
finishTime: new siProtocol.SiTime([[0x13], [0x12], [0x10]]),
|
|
90
|
+
checkTime: new siProtocol.SiTime([[0x0b], [0x0a], [0x08]]),
|
|
91
|
+
punchCount: new storage.SiInt([[0x16]]),
|
|
92
|
+
punches: new storage.SiModified(
|
|
93
|
+
new storage.SiArray(
|
|
94
|
+
MAX_NUM_PUNCHES,
|
|
95
|
+
(i) =>
|
|
96
|
+
new storage.SiDict({
|
|
97
|
+
code: new storage.SiInt([[getPunchOffset(i) + 1]]),
|
|
98
|
+
time: new siProtocol.SiTime([[getPunchOffset(i) + 3], [getPunchOffset(i) + 2], [getPunchOffset(i) + 0]])
|
|
99
|
+
})
|
|
100
|
+
),
|
|
101
|
+
(allPunches) => cropPunches(allPunches)
|
|
102
|
+
),
|
|
103
|
+
cardHolder: new storage.SiModified(new storage.SiArray(0x80, (i) => new storage.SiInt([[0x20 + i]])), (charCodes) => parseCardHolder(charCodes))
|
|
104
|
+
};
|
|
105
|
+
export const modernSiCardStorageDefinition = storage.defineStorage(0x400, modernSiCardStorageLocations);
|
|
106
|
+
|
|
107
|
+
export class ModernSiCard extends BaseSiCard {
|
|
108
|
+
static maxNumPunches = MAX_NUM_PUNCHES;
|
|
109
|
+
|
|
110
|
+
static parseModernSiCardDetectionMessage(message: siProtocol.SiMessage): { cardNumber: number; cardSeries: keyof typeof ModernSiCardSeries } | undefined {
|
|
111
|
+
if (message.mode !== undefined) {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
if (message.command !== proto.cmd.SI8_DET) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
if (message.parameters.length < 6) {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
const cardNumber = siProtocol.arr2cardNumber([message.parameters[5], message.parameters[4], message.parameters[3]]);
|
|
121
|
+
/* istanbul ignore next */
|
|
122
|
+
if (cardNumber === undefined) {
|
|
123
|
+
throw new Error('card number cannot be undefined');
|
|
124
|
+
}
|
|
125
|
+
const cardSeries = message.parameters[2];
|
|
126
|
+
const lookup = utils.getLookup(ModernSiCardSeries);
|
|
127
|
+
return {
|
|
128
|
+
cardNumber: cardNumber,
|
|
129
|
+
cardSeries: lookup[cardSeries] as keyof typeof ModernSiCardSeries
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public storage: storage.ISiStorage<IModernSiCardStorageFields>;
|
|
134
|
+
|
|
135
|
+
public punchCount?: number;
|
|
136
|
+
public cardSeries?: keyof typeof ModernSiCardSeries;
|
|
137
|
+
|
|
138
|
+
constructor(cardNumber: number) {
|
|
139
|
+
super(cardNumber);
|
|
140
|
+
this.storage = modernSiCardStorageDefinition();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
typeSpecificGetPage(pageNumber: number): Promise<number[]> {
|
|
144
|
+
if (!this.mainStation) {
|
|
145
|
+
return Promise.reject(new Error('No main station'));
|
|
146
|
+
}
|
|
147
|
+
return this.mainStation
|
|
148
|
+
.sendMessage(
|
|
149
|
+
{
|
|
150
|
+
command: proto.cmd.GET_SI8,
|
|
151
|
+
parameters: [pageNumber]
|
|
152
|
+
},
|
|
153
|
+
1
|
|
154
|
+
)
|
|
155
|
+
.then((data: number[][]) => {
|
|
156
|
+
console.assert(data[0][2] === pageNumber, `Page number ${data[0][2]} retrieved (expected ${pageNumber})`);
|
|
157
|
+
return data[0].slice(3);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
typeSpecificRead(): Promise<void> {
|
|
162
|
+
return new Promise((resolve, reject) => {
|
|
163
|
+
this.typeSpecificReadBasic()
|
|
164
|
+
.then(() => this.typeSpecificReadCardHolder())
|
|
165
|
+
.then(() => this.typeSpecificReadPunches())
|
|
166
|
+
.then(() => {
|
|
167
|
+
this.raceResult = {
|
|
168
|
+
cardNumber: this.storage.get('cardNumber')?.value || 0,
|
|
169
|
+
startTime: this.storage.get('startTime')?.value,
|
|
170
|
+
finishTime: this.storage.get('finishTime')?.value,
|
|
171
|
+
checkTime: this.storage.get('checkTime')?.value,
|
|
172
|
+
punches: this.storage.get('punches')?.value,
|
|
173
|
+
cardHolder: this.storage.get('cardHolder')?.value
|
|
174
|
+
};
|
|
175
|
+
this.punchCount = this.storage.get('punchCount')?.value;
|
|
176
|
+
this.cardSeries = this.storage.get('cardSeries')?.value;
|
|
177
|
+
resolve();
|
|
178
|
+
})
|
|
179
|
+
.catch((exc: Error) => reject(exc));
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
typeSpecificReadBasic(): Promise<void> {
|
|
184
|
+
return this.typeSpecificGetPage(0).then((page0: number[]) => {
|
|
185
|
+
this.storage.splice(bytesPerPage * 0, bytesPerPage, ...page0);
|
|
186
|
+
|
|
187
|
+
const readCardNumber = this.storage.get('cardNumber')!.value;
|
|
188
|
+
if (this.cardNumber !== readCardNumber) {
|
|
189
|
+
console.warn(`ModernSiCard Number ${readCardNumber} (expected ${this.cardNumber})`);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
typeSpecificReadCardHolder(): Promise<void> {
|
|
195
|
+
const cardHolderSoFar = this.storage.get('cardHolder')!.value;
|
|
196
|
+
if (cardHolderSoFar && cardHolderSoFar.isComplete) {
|
|
197
|
+
return Promise.resolve();
|
|
198
|
+
}
|
|
199
|
+
return this.typeSpecificGetPage(1).then((page1: number[]) => {
|
|
200
|
+
this.storage.splice(bytesPerPage * 1, bytesPerPage, ...page1);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
typeSpecificReadPunches(): Promise<void> {
|
|
205
|
+
if (this.storage.get('punchCount')!.value <= punchesPerPage * 0) {
|
|
206
|
+
return Promise.resolve();
|
|
207
|
+
}
|
|
208
|
+
return this.typeSpecificGetPage(4)
|
|
209
|
+
.then((page4: number[]) => {
|
|
210
|
+
this.storage.splice(bytesPerPage * 4, bytesPerPage, ...page4);
|
|
211
|
+
if (this.storage.get('punchCount')!.value <= punchesPerPage * 1) {
|
|
212
|
+
throw new ReadFinishedException();
|
|
213
|
+
}
|
|
214
|
+
return this.typeSpecificGetPage(5);
|
|
215
|
+
})
|
|
216
|
+
.then((page5: number[]) => {
|
|
217
|
+
this.storage.splice(bytesPerPage * 5, bytesPerPage, ...page5);
|
|
218
|
+
if (this.storage.get('punchCount')!.value <= punchesPerPage * 2) {
|
|
219
|
+
throw new ReadFinishedException();
|
|
220
|
+
}
|
|
221
|
+
return this.typeSpecificGetPage(6);
|
|
222
|
+
})
|
|
223
|
+
.then((page6: number[]) => {
|
|
224
|
+
this.storage.splice(bytesPerPage * 6, bytesPerPage, ...page6);
|
|
225
|
+
if (this.storage.get('punchCount')!.value <= punchesPerPage * 3) {
|
|
226
|
+
throw new ReadFinishedException();
|
|
227
|
+
}
|
|
228
|
+
return this.typeSpecificGetPage(7);
|
|
229
|
+
})
|
|
230
|
+
.then((page7: number[]) => {
|
|
231
|
+
this.storage.splice(bytesPerPage * 7, bytesPerPage, ...page7);
|
|
232
|
+
throw new ReadFinishedException();
|
|
233
|
+
})
|
|
234
|
+
.catch((exc: Error) => {
|
|
235
|
+
if (exc instanceof ReadFinishedException) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
throw exc;
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, test } from '@jest/globals';
|
|
2
|
+
import { BaseSiCard } from '../BaseSiCard';
|
|
3
|
+
import { PCard } from './PCard';
|
|
4
|
+
|
|
5
|
+
describe('PCard', () => {
|
|
6
|
+
test('is registered', () => {
|
|
7
|
+
expect(BaseSiCard.getTypeByCardNumber(3999999)).not.toEqual(PCard);
|
|
8
|
+
expect(BaseSiCard.getTypeByCardNumber(4000000)).toEqual(PCard);
|
|
9
|
+
expect(BaseSiCard.getTypeByCardNumber(4999999)).toEqual(PCard);
|
|
10
|
+
expect(BaseSiCard.getTypeByCardNumber(5000000)).not.toEqual(PCard);
|
|
11
|
+
});
|
|
12
|
+
test('is not implemented', (done) => {
|
|
13
|
+
const pCard = new PCard(4000000);
|
|
14
|
+
pCard.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 PCard extends BaseSiCard {
|
|
6
|
+
static typeSpecificInstanceFromMessage(_message: siProtocol.SiMessage): PCard | undefined {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
typeSpecificRead(): Promise<void> {
|
|
11
|
+
return Promise.reject(new utils.NotImplementedError());
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
BaseSiCard.registerNumberRange(4000000, 5000000, PCard);
|
|
@@ -0,0 +1,84 @@
|
|
|
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 { SIAC } from './SIAC';
|
|
8
|
+
|
|
9
|
+
describe('SIAC', () => {
|
|
10
|
+
test('is registered', () => {
|
|
11
|
+
expect(BaseSiCard.getTypeByCardNumber(7999999)).not.toEqual(SIAC);
|
|
12
|
+
expect(BaseSiCard.getTypeByCardNumber(8000000)).toEqual(SIAC);
|
|
13
|
+
expect(BaseSiCard.getTypeByCardNumber(8999999)).toEqual(SIAC);
|
|
14
|
+
expect(BaseSiCard.getTypeByCardNumber(9000000)).not.toEqual(SIAC);
|
|
15
|
+
});
|
|
16
|
+
describe('typeSpecificInstanceFromMessage', () => {
|
|
17
|
+
test('works for valid message', () => {
|
|
18
|
+
const instance = SIAC.typeSpecificInstanceFromMessage({
|
|
19
|
+
command: proto.cmd.SI8_DET,
|
|
20
|
+
parameters: [0x00, 0x00, /* TODO: */ 0x00, 0x88, 0x88, 0x88]
|
|
21
|
+
});
|
|
22
|
+
if (instance === undefined) {
|
|
23
|
+
throw new Error('expect instance');
|
|
24
|
+
}
|
|
25
|
+
expect(instance instanceof SIAC).toBe(true);
|
|
26
|
+
expect(instance.cardNumber).toBe(8947848);
|
|
27
|
+
});
|
|
28
|
+
test('returns undefined when message has mode', () => {
|
|
29
|
+
expect(
|
|
30
|
+
SIAC.typeSpecificInstanceFromMessage({
|
|
31
|
+
mode: proto.NAK
|
|
32
|
+
})
|
|
33
|
+
).toBe(undefined);
|
|
34
|
+
});
|
|
35
|
+
test('returns undefined when message has wrong command', () => {
|
|
36
|
+
expect(
|
|
37
|
+
SIAC.typeSpecificInstanceFromMessage({
|
|
38
|
+
command: testUtils.getRandomByteExcept([proto.cmd.SI8_DET]),
|
|
39
|
+
parameters: []
|
|
40
|
+
})
|
|
41
|
+
).toBe(undefined);
|
|
42
|
+
});
|
|
43
|
+
test('returns undefined when there are too few parameters', () => {
|
|
44
|
+
expect(
|
|
45
|
+
SIAC.typeSpecificInstanceFromMessage({
|
|
46
|
+
command: proto.cmd.SI8_DET,
|
|
47
|
+
parameters: []
|
|
48
|
+
})
|
|
49
|
+
).toBe(undefined);
|
|
50
|
+
});
|
|
51
|
+
test('returns undefined when the card number does not match', () => {
|
|
52
|
+
expect(
|
|
53
|
+
SIAC.typeSpecificInstanceFromMessage({
|
|
54
|
+
command: proto.cmd.SI8_DET,
|
|
55
|
+
parameters: [0x00, 0x00, /* TODO: */ 0x00, 0x22, 0x22, 0x22]
|
|
56
|
+
})
|
|
57
|
+
).toBe(undefined);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
test('is modern', (done) => {
|
|
61
|
+
const mySIAC = new SIAC(8500000);
|
|
62
|
+
mySIAC.mainStation = {
|
|
63
|
+
sendMessage: (message: siProtocol.SiMessage, numResponses?: number) => {
|
|
64
|
+
if (message.mode !== undefined) {
|
|
65
|
+
return Promise.reject(new Error('message mode is not undefined'));
|
|
66
|
+
}
|
|
67
|
+
const { command, parameters } = message;
|
|
68
|
+
expect(command).toBe(proto.cmd.GET_SI8);
|
|
69
|
+
expect(numResponses).toBe(1);
|
|
70
|
+
const pageNumberToGet = parameters[0];
|
|
71
|
+
const getPage = (pageNumber: number) => [...[0x00, 0x00, pageNumber], ..._.range(128).map(() => 0)];
|
|
72
|
+
return Promise.resolve([getPage(pageNumberToGet)]);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
mySIAC.typeSpecificRead().then(() => {
|
|
76
|
+
expect(mySIAC.raceResult.cardNumber).toBe(0);
|
|
77
|
+
expect(JSON.stringify(mySIAC.raceResult.startTime)).toBe(JSON.stringify({"time":0,"weekcounter":0,"weekday":0}));
|
|
78
|
+
expect(JSON.stringify(mySIAC.raceResult.finishTime)).toBe(JSON.stringify({"time":0,"weekcounter":0,"weekday":0}));
|
|
79
|
+
expect(mySIAC.raceResult.punches).toEqual([]);
|
|
80
|
+
expect(mySIAC.punchCount).toBe(0);
|
|
81
|
+
done();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type * as siProtocol from '../../siProtocol';
|
|
2
|
+
import { ModernSiCard } from './ModernSiCard';
|
|
3
|
+
import { BaseSiCard } from '../BaseSiCard';
|
|
4
|
+
|
|
5
|
+
export class SIAC extends ModernSiCard {
|
|
6
|
+
static typeSpecificInstanceFromMessage(message: siProtocol.SiMessage): SIAC | undefined {
|
|
7
|
+
const info = this.parseModernSiCardDetectionMessage(message);
|
|
8
|
+
if (info === undefined) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
// if (info.cardSeries !== ModernSiCardSeries.SIAC) {
|
|
12
|
+
// TODO: find out the series value and remove this hack
|
|
13
|
+
if (info.cardNumber < 8000000 || info.cardNumber >= 9000000) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
return new this(info.cardNumber);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
BaseSiCard.registerNumberRange(8000000, 9000000, SIAC);
|
|
@@ -0,0 +1,85 @@
|
|
|
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 { SiCard10 } from './SiCard10';
|
|
9
|
+
|
|
10
|
+
describe('SiCard10', () => {
|
|
11
|
+
test('is registered', () => {
|
|
12
|
+
expect(BaseSiCard.getTypeByCardNumber(6999999)).not.toEqual(SiCard10);
|
|
13
|
+
expect(BaseSiCard.getTypeByCardNumber(7000000)).toEqual(SiCard10);
|
|
14
|
+
expect(BaseSiCard.getTypeByCardNumber(7999999)).toEqual(SiCard10);
|
|
15
|
+
expect(BaseSiCard.getTypeByCardNumber(8000000)).not.toEqual(SiCard10);
|
|
16
|
+
});
|
|
17
|
+
describe('typeSpecificInstanceFromMessage', () => {
|
|
18
|
+
test('works for valid message', () => {
|
|
19
|
+
const instance = SiCard10.typeSpecificInstanceFromMessage({
|
|
20
|
+
command: proto.cmd.SI8_DET,
|
|
21
|
+
parameters: [0x00, 0x00, ModernSiCardSeries.SiCard10, 0x77, 0x77, 0x77]
|
|
22
|
+
});
|
|
23
|
+
if (instance === undefined) {
|
|
24
|
+
throw new Error('expect instance');
|
|
25
|
+
}
|
|
26
|
+
expect(instance instanceof SiCard10).toBe(true);
|
|
27
|
+
expect(instance.cardNumber).toBe(7829367);
|
|
28
|
+
});
|
|
29
|
+
test('returns undefined when message has mode', () => {
|
|
30
|
+
expect(
|
|
31
|
+
SiCard10.typeSpecificInstanceFromMessage({
|
|
32
|
+
mode: proto.NAK
|
|
33
|
+
})
|
|
34
|
+
).toBe(undefined);
|
|
35
|
+
});
|
|
36
|
+
test('returns undefined when message has wrong command', () => {
|
|
37
|
+
expect(
|
|
38
|
+
SiCard10.typeSpecificInstanceFromMessage({
|
|
39
|
+
command: testUtils.getRandomByteExcept([proto.cmd.SI8_DET]),
|
|
40
|
+
parameters: []
|
|
41
|
+
})
|
|
42
|
+
).toBe(undefined);
|
|
43
|
+
});
|
|
44
|
+
test('returns undefined when there are too few parameters', () => {
|
|
45
|
+
expect(
|
|
46
|
+
SiCard10.typeSpecificInstanceFromMessage({
|
|
47
|
+
command: proto.cmd.SI8_DET,
|
|
48
|
+
parameters: []
|
|
49
|
+
})
|
|
50
|
+
).toBe(undefined);
|
|
51
|
+
});
|
|
52
|
+
test('returns undefined when the series does not match', () => {
|
|
53
|
+
expect(
|
|
54
|
+
SiCard10.typeSpecificInstanceFromMessage({
|
|
55
|
+
command: proto.cmd.SI8_DET,
|
|
56
|
+
parameters: [0x00, 0x00, testUtils.getRandomByteExcept([ModernSiCardSeries.SiCard10]), 0x22, 0x22, 0x22]
|
|
57
|
+
})
|
|
58
|
+
).toBe(undefined);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
test('is modern', (done) => {
|
|
62
|
+
const mySiCard10 = new SiCard10(8500000);
|
|
63
|
+
mySiCard10.mainStation = {
|
|
64
|
+
sendMessage: (message: siProtocol.SiMessage, numResponses?: number) => {
|
|
65
|
+
if (message.mode !== undefined) {
|
|
66
|
+
return Promise.reject(new Error('message mode is not undefined'));
|
|
67
|
+
}
|
|
68
|
+
const { command, parameters } = message;
|
|
69
|
+
expect(command).toBe(proto.cmd.GET_SI8);
|
|
70
|
+
expect(numResponses).toBe(1);
|
|
71
|
+
const pageNumberToGet = parameters[0];
|
|
72
|
+
const getPage = (pageNumber: number) => [...[0x00, 0x00, pageNumber], ..._.range(128).map(() => 0)];
|
|
73
|
+
return Promise.resolve([getPage(pageNumberToGet)]);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
mySiCard10.typeSpecificRead().then(() => {
|
|
77
|
+
expect(mySiCard10.raceResult.cardNumber).toBe(0);
|
|
78
|
+
expect(JSON.stringify(mySiCard10.raceResult.startTime)).toBe(JSON.stringify({"time":0,"weekcounter":0,"weekday":0}));
|
|
79
|
+
expect(JSON.stringify(mySiCard10.raceResult.finishTime)).toBe(JSON.stringify({"time":0,"weekcounter":0,"weekday":0}));
|
|
80
|
+
expect(mySiCard10.raceResult.punches).toEqual([]);
|
|
81
|
+
expect(mySiCard10.punchCount).toBe(0);
|
|
82
|
+
done();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type * as siProtocol from '../../siProtocol';
|
|
2
|
+
import { ModernSiCard } from './ModernSiCard';
|
|
3
|
+
import { BaseSiCard } from '../BaseSiCard';
|
|
4
|
+
|
|
5
|
+
export class SiCard10 extends ModernSiCard {
|
|
6
|
+
static typeSpecificInstanceFromMessage(message: siProtocol.SiMessage): SiCard10 | undefined {
|
|
7
|
+
const info = this.parseModernSiCardDetectionMessage(message);
|
|
8
|
+
if (info === undefined) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
if (info.cardSeries !== 'SiCard10') {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
return new this(info.cardNumber);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
BaseSiCard.registerNumberRange(7000000, 8000000, SiCard10);
|
|
@@ -0,0 +1,84 @@
|
|
|
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 { SiCard11 } from './SiCard11';
|
|
8
|
+
|
|
9
|
+
describe('SiCard11', () => {
|
|
10
|
+
test('is registered', () => {
|
|
11
|
+
expect(BaseSiCard.getTypeByCardNumber(8999999)).not.toEqual(SiCard11);
|
|
12
|
+
expect(BaseSiCard.getTypeByCardNumber(9000000)).toEqual(SiCard11);
|
|
13
|
+
expect(BaseSiCard.getTypeByCardNumber(9999999)).toEqual(SiCard11);
|
|
14
|
+
expect(BaseSiCard.getTypeByCardNumber(10000000)).not.toEqual(SiCard11);
|
|
15
|
+
});
|
|
16
|
+
describe('typeSpecificInstanceFromMessage', () => {
|
|
17
|
+
test('works for valid message', () => {
|
|
18
|
+
const instance = SiCard11.typeSpecificInstanceFromMessage({
|
|
19
|
+
command: proto.cmd.SI8_DET,
|
|
20
|
+
parameters: [0x00, 0x00, /* TODO: */ 0x00, 0x98, 0x76, 0x54]
|
|
21
|
+
});
|
|
22
|
+
if (instance === undefined) {
|
|
23
|
+
throw new Error('expect instance');
|
|
24
|
+
}
|
|
25
|
+
expect(instance instanceof SiCard11).toBe(true);
|
|
26
|
+
expect(instance.cardNumber).toBe(9991764);
|
|
27
|
+
});
|
|
28
|
+
test('returns undefined when message has mode', () => {
|
|
29
|
+
expect(
|
|
30
|
+
SiCard11.typeSpecificInstanceFromMessage({
|
|
31
|
+
mode: proto.NAK
|
|
32
|
+
})
|
|
33
|
+
).toBe(undefined);
|
|
34
|
+
});
|
|
35
|
+
test('returns undefined when message has wrong command', () => {
|
|
36
|
+
expect(
|
|
37
|
+
SiCard11.typeSpecificInstanceFromMessage({
|
|
38
|
+
command: testUtils.getRandomByteExcept([proto.cmd.SI8_DET]),
|
|
39
|
+
parameters: []
|
|
40
|
+
})
|
|
41
|
+
).toBe(undefined);
|
|
42
|
+
});
|
|
43
|
+
test('returns undefined when there are too few parameters', () => {
|
|
44
|
+
expect(
|
|
45
|
+
SiCard11.typeSpecificInstanceFromMessage({
|
|
46
|
+
command: proto.cmd.SI8_DET,
|
|
47
|
+
parameters: []
|
|
48
|
+
})
|
|
49
|
+
).toBe(undefined);
|
|
50
|
+
});
|
|
51
|
+
test('returns undefined when the card number does not match', () => {
|
|
52
|
+
expect(
|
|
53
|
+
SiCard11.typeSpecificInstanceFromMessage({
|
|
54
|
+
command: proto.cmd.SI8_DET,
|
|
55
|
+
parameters: [0x00, 0x00, /* TODO: */ 0x00, 0x22, 0x22, 0x22]
|
|
56
|
+
})
|
|
57
|
+
).toBe(undefined);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
test('is modern', (done) => {
|
|
61
|
+
const mySiCard11 = new SiCard11(8500000);
|
|
62
|
+
mySiCard11.mainStation = {
|
|
63
|
+
sendMessage: (message: siProtocol.SiMessage, numResponses?: number) => {
|
|
64
|
+
if (message.mode !== undefined) {
|
|
65
|
+
return Promise.reject(new Error('message mode is not undefined'));
|
|
66
|
+
}
|
|
67
|
+
const { command, parameters } = message;
|
|
68
|
+
expect(command).toBe(proto.cmd.GET_SI8);
|
|
69
|
+
expect(numResponses).toBe(1);
|
|
70
|
+
const pageNumberToGet = parameters[0];
|
|
71
|
+
const getPage = (pageNumber: number) => [...[0x00, 0x00, pageNumber], ..._.range(128).map(() => 0)];
|
|
72
|
+
return Promise.resolve([getPage(pageNumberToGet)]);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
mySiCard11.typeSpecificRead().then(() => {
|
|
76
|
+
expect(mySiCard11.raceResult.cardNumber).toBe(0);
|
|
77
|
+
expect(JSON.stringify(mySiCard11.raceResult.startTime)).toBe(JSON.stringify({"time":0,"weekcounter":0,"weekday":0}));
|
|
78
|
+
expect(JSON.stringify(mySiCard11.raceResult.finishTime)).toBe(JSON.stringify({"time":0,"weekcounter":0,"weekday":0}));
|
|
79
|
+
expect(mySiCard11.raceResult.punches).toEqual([]);
|
|
80
|
+
expect(mySiCard11.punchCount).toBe(0);
|
|
81
|
+
done();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type * as siProtocol from '../../siProtocol';
|
|
2
|
+
import { ModernSiCard } from './ModernSiCard';
|
|
3
|
+
import { BaseSiCard } from '../BaseSiCard';
|
|
4
|
+
|
|
5
|
+
export class SiCard11 extends ModernSiCard {
|
|
6
|
+
static typeSpecificInstanceFromMessage(message: siProtocol.SiMessage): SiCard11 | undefined {
|
|
7
|
+
const info = this.parseModernSiCardDetectionMessage(message);
|
|
8
|
+
if (info === undefined) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
// if (info.cardSeries !== ModernSiCardSeries.SiCard11) {
|
|
12
|
+
// TODO: find out the series value and remove this hack
|
|
13
|
+
if (info.cardNumber < 9000000 || info.cardNumber >= 10000000) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
return new this(info.cardNumber);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
BaseSiCard.registerNumberRange(9000000, 10000000, SiCard11);
|