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 { proto } from '../constants';
|
|
2
|
+
import * as utils from '../utils';
|
|
3
|
+
import * as siProtocol from '../siProtocol';
|
|
4
|
+
import type { ISiStation } from './ISiStation';
|
|
5
|
+
import {
|
|
6
|
+
type _ISiTargetMultiplexerTestData,
|
|
7
|
+
type ISiTargetMultiplexer,
|
|
8
|
+
SiTargetMultiplexerDirectMessageEvent,
|
|
9
|
+
type SiTargetMultiplexerEvents,
|
|
10
|
+
SiTargetMultiplexerMessageEvent,
|
|
11
|
+
SiTargetMultiplexerRemoteMessageEvent,
|
|
12
|
+
SiTargetMultiplexerTarget
|
|
13
|
+
} from './ISiTargetMultiplexer';
|
|
14
|
+
import { type ISiDevice, type ISiDeviceDriverData, SiDeviceState } from '../SiDevice/ISiDevice';
|
|
15
|
+
import { SiSendTaskState } from './ISiSendTask';
|
|
16
|
+
import { SiSendTask } from './SiSendTask';
|
|
17
|
+
|
|
18
|
+
/** Commands that can only be sent from a direct station. */
|
|
19
|
+
export const DIRECT_DEVICE_INITIATED_COMMANDS: { [command: number]: boolean } = {
|
|
20
|
+
[proto.cmd.TRANS_REC]: true,
|
|
21
|
+
[proto.cmd.SI5_DET]: true,
|
|
22
|
+
[proto.cmd.SI6_DET]: true,
|
|
23
|
+
[proto.cmd.SI8_DET]: true,
|
|
24
|
+
[proto.cmd.SI_REM]: true
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Commands that are sent from a station without prior request. */
|
|
28
|
+
const DEVICE_INITIATED_COMMANDS: { [command: number]: boolean } = {
|
|
29
|
+
...DIRECT_DEVICE_INITIATED_COMMANDS,
|
|
30
|
+
[proto.cmd.SRR_PING]: true
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export class SiTargetMultiplexer implements ISiTargetMultiplexer {
|
|
34
|
+
static fromSiDevice(siDevice: ISiDevice<ISiDeviceDriverData<unknown>>): ISiTargetMultiplexer {
|
|
35
|
+
if (siDevice.siTargetMultiplexer) {
|
|
36
|
+
return siDevice.siTargetMultiplexer;
|
|
37
|
+
}
|
|
38
|
+
const instance = new this(siDevice);
|
|
39
|
+
siDevice.siTargetMultiplexer = instance;
|
|
40
|
+
siDevice.addEventListener('stateChange', (e) => {
|
|
41
|
+
instance.handleDeviceStateChange(e.state);
|
|
42
|
+
});
|
|
43
|
+
siDevice.addEventListener('receive', (e) => {
|
|
44
|
+
instance.handleReceive(e.uint8Data);
|
|
45
|
+
});
|
|
46
|
+
// TODO: deregister/close
|
|
47
|
+
return instance;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public stations: { [Target in SiTargetMultiplexerTarget]?: ISiStation<Target> } = {};
|
|
51
|
+
public target: SiTargetMultiplexerTarget = SiTargetMultiplexerTarget.Unknown;
|
|
52
|
+
// the target of the latest command scheduled
|
|
53
|
+
public latestTarget: SiTargetMultiplexerTarget = SiTargetMultiplexerTarget.Unknown;
|
|
54
|
+
private receiveBuffer: number[] = [];
|
|
55
|
+
private sendQueue: SiSendTask[] = [];
|
|
56
|
+
|
|
57
|
+
// eslint-disable-next-line no-useless-constructor
|
|
58
|
+
constructor(
|
|
59
|
+
public siDevice: ISiDevice<ISiDeviceDriverData<unknown>> // eslint-disable-next-line no-empty-function
|
|
60
|
+
) {}
|
|
61
|
+
|
|
62
|
+
get _test(): _ISiTargetMultiplexerTestData {
|
|
63
|
+
return {
|
|
64
|
+
latestTarget: this.latestTarget,
|
|
65
|
+
sendQueue: this.sendQueue
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
handleDeviceStateChange(newState: SiDeviceState): void {
|
|
70
|
+
const actionByNewState: { [state in SiDeviceState]: () => void } = {
|
|
71
|
+
[SiDeviceState.Opening]: () => undefined,
|
|
72
|
+
[SiDeviceState.Opened]: () => this.startProcessingSendQueue(),
|
|
73
|
+
[SiDeviceState.Closing]: () => this.abortProcessingSendQueue(),
|
|
74
|
+
[SiDeviceState.Closed]: () => this.abortProcessingSendQueue()
|
|
75
|
+
};
|
|
76
|
+
const actionToPerform = actionByNewState[newState];
|
|
77
|
+
if (actionToPerform) {
|
|
78
|
+
actionToPerform();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
startProcessingSendQueue(): void {
|
|
83
|
+
setTimeout(() => this._processSendQueue(), 1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
abortProcessingSendQueue(): void {
|
|
87
|
+
this.sendQueue.forEach((sendTask) => {
|
|
88
|
+
sendTask.fail();
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
handleReceive(uint8Data: number[]): void {
|
|
93
|
+
this.receiveBuffer.push(...uint8Data);
|
|
94
|
+
const { messages, remainder } = siProtocol.parseAll(this.receiveBuffer);
|
|
95
|
+
this.receiveBuffer = remainder;
|
|
96
|
+
messages.forEach((message) => {
|
|
97
|
+
this.updateSendQueueWithReceivedMessage(message);
|
|
98
|
+
this.dispatchEvent('message', new SiTargetMultiplexerMessageEvent(this, message));
|
|
99
|
+
if (this.target === SiTargetMultiplexerTarget.Direct) {
|
|
100
|
+
this.dispatchEvent('directMessage', new SiTargetMultiplexerDirectMessageEvent(this, message));
|
|
101
|
+
}
|
|
102
|
+
if (this.target === SiTargetMultiplexerTarget.Remote) {
|
|
103
|
+
this.dispatchEvent('remoteMessage', new SiTargetMultiplexerRemoteMessageEvent(this, message));
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
updateSendQueueWithReceivedMessage(message: siProtocol.SiMessage): void {
|
|
109
|
+
if (message.mode === undefined && DIRECT_DEVICE_INITIATED_COMMANDS[message.command]) {
|
|
110
|
+
console.debug('Received direct device-initiated command. Assuming target Direct...');
|
|
111
|
+
this.target = SiTargetMultiplexerTarget.Direct;
|
|
112
|
+
this.latestTarget = SiTargetMultiplexerTarget.Direct;
|
|
113
|
+
}
|
|
114
|
+
if (this.sendQueue.length === 0) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const shouldAcceptResponseInState: { [state in SiSendTaskState]: boolean } = {
|
|
118
|
+
[SiSendTaskState.Queued]: false,
|
|
119
|
+
[SiSendTaskState.Sending]: true, // This is mostly for testing purposes
|
|
120
|
+
[SiSendTaskState.Sent]: true,
|
|
121
|
+
[SiSendTaskState.Succeeded]: false,
|
|
122
|
+
[SiSendTaskState.Failed]: false
|
|
123
|
+
};
|
|
124
|
+
if (!shouldAcceptResponseInState[this.sendQueue[0].state]) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (message.mode === proto.NAK) {
|
|
128
|
+
this.sendQueue[0].fail();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (message.mode !== undefined) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const expectedMessage = this.sendQueue[0].message;
|
|
135
|
+
if (expectedMessage.mode !== undefined) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (message.command !== expectedMessage.command) {
|
|
139
|
+
if (DEVICE_INITIATED_COMMANDS[message.command] !== true) {
|
|
140
|
+
console.warn(`Strange Response: expected ${utils.prettyHex([expectedMessage.command])}, but got ${utils.prettyHex([message.command])}...`);
|
|
141
|
+
}
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
this.sendQueue[0].addResponse(message.parameters);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async sendMessage(target: SiTargetMultiplexerTarget, message: siProtocol.SiMessage, numResponses?: number, timeoutInMiliseconds?: number): Promise<number[][]> {
|
|
148
|
+
console.warn("sendMessage to ",target)
|
|
149
|
+
return this.setTarget(target).then(() => this.sendMessageToLatestTarget(message, numResponses, timeoutInMiliseconds)).catch(e=>{console.warn(e); return Promise.reject(e)})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async setTarget(target: SiTargetMultiplexerTarget): Promise<void> {
|
|
153
|
+
console.warn("set Target to ",target, "last is ",this.latestTarget)
|
|
154
|
+
if (target === this.latestTarget) {
|
|
155
|
+
return Promise.resolve();
|
|
156
|
+
}
|
|
157
|
+
const setMsParameterByTarget: { [Target in SiTargetMultiplexerTarget]: number | undefined } = {
|
|
158
|
+
[SiTargetMultiplexerTarget.Direct]: proto.P_MS_DIRECT,
|
|
159
|
+
[SiTargetMultiplexerTarget.Remote]: proto.P_MS_REMOTE,
|
|
160
|
+
[SiTargetMultiplexerTarget.Unknown]: undefined,
|
|
161
|
+
[SiTargetMultiplexerTarget.Switching]: undefined
|
|
162
|
+
};
|
|
163
|
+
const setMsParameter = setMsParameterByTarget[target];
|
|
164
|
+
if (setMsParameter == undefined) {
|
|
165
|
+
console.warn("target is undefined, raise error")
|
|
166
|
+
return Promise.reject(new Error(`No such target: ${target}`));
|
|
167
|
+
}
|
|
168
|
+
this.latestTarget = target;
|
|
169
|
+
return this.sendMessageToLatestTarget(
|
|
170
|
+
{
|
|
171
|
+
command: proto.cmd.SET_MS,
|
|
172
|
+
parameters: [setMsParameter]
|
|
173
|
+
},
|
|
174
|
+
1
|
|
175
|
+
)
|
|
176
|
+
.then((responses: number[][]) => {
|
|
177
|
+
if (responses[0][2] !== setMsParameter) {
|
|
178
|
+
throw new Error('contradicting SET_MS response');
|
|
179
|
+
}
|
|
180
|
+
this.target = target;
|
|
181
|
+
})
|
|
182
|
+
.catch((err) => {
|
|
183
|
+
this.abortProcessingSendQueue();
|
|
184
|
+
this.target = SiTargetMultiplexerTarget.Unknown;
|
|
185
|
+
throw err;
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
sendMessageToLatestTarget(message: siProtocol.SiMessage, numResponses = 0, timeoutInMiliseconds = 10000): Promise<number[][]> {
|
|
190
|
+
return new Promise((resolve, reject) => {
|
|
191
|
+
const sendTask = new SiSendTask(
|
|
192
|
+
message,
|
|
193
|
+
numResponses,
|
|
194
|
+
timeoutInMiliseconds,
|
|
195
|
+
(resolvedTask) => {
|
|
196
|
+
const shifted = this.sendQueue.shift();
|
|
197
|
+
if (resolvedTask !== shifted) {
|
|
198
|
+
throw new Error('Resolved unexecuting SendTask');
|
|
199
|
+
}
|
|
200
|
+
setTimeout(() => this._processSendQueue(), 1);
|
|
201
|
+
resolve(resolvedTask.responses);
|
|
202
|
+
},
|
|
203
|
+
(rejectedTask) => {
|
|
204
|
+
const shifted = this.sendQueue.shift();
|
|
205
|
+
if (rejectedTask !== shifted) {
|
|
206
|
+
throw new Error('Rejected unexecuting SendTask');
|
|
207
|
+
}
|
|
208
|
+
setTimeout(() => this._processSendQueue(), 1);
|
|
209
|
+
reject(new Error('Rejection'));
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
this.sendQueue.push(sendTask);
|
|
213
|
+
this._processSendQueue();
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
_processSendQueue(): void {
|
|
218
|
+
if (this.sendQueue.length === 0 || this.sendQueue[0].state === SiSendTaskState.Sending || this.sendQueue[0].state === SiSendTaskState.Sent || this.siDevice.state !== SiDeviceState.Opened) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const sendTask = this.sendQueue[0];
|
|
223
|
+
sendTask.state = SiSendTaskState.Sending;
|
|
224
|
+
const uint8Data = [proto.WAKEUP, ...siProtocol.render(sendTask.message)];
|
|
225
|
+
this.siDevice
|
|
226
|
+
.send(uint8Data)
|
|
227
|
+
.then(() => {
|
|
228
|
+
sendTask.state = SiSendTaskState.Sent;
|
|
229
|
+
if (sendTask.numResponses <= 0) {
|
|
230
|
+
sendTask.succeed();
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
.catch((err: utils.SiError) => {
|
|
234
|
+
console.warn(`Error sending: ${err}`);
|
|
235
|
+
sendTask.fail();
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
240
|
+
export interface SiTargetMultiplexer extends utils.EventTarget<SiTargetMultiplexerEvents> {}
|
|
241
|
+
utils.applyMixins(SiTargetMultiplexer, [utils.EventTarget]);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { SiTargetMultiplexer } from './SiTargetMultiplexer';
|
|
2
|
+
export { BaseSiStation } from './BaseSiStation';
|
|
3
|
+
export { SiMainStation } from './SiMainStation';
|
|
4
|
+
export { CoupledSiStation } from './CoupledSiStation';
|
|
5
|
+
export { getSiStationExamples } from './siStationExamples';
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import * as utils from '../utils';
|
|
2
|
+
|
|
3
|
+
const cache = {};
|
|
4
|
+
|
|
5
|
+
export interface StationSample {
|
|
6
|
+
stationData: { [attr: string]: unknown };
|
|
7
|
+
storageData: (number | undefined)[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const getBSM8Station = utils.cached(cache, () => ({
|
|
11
|
+
stationData: {
|
|
12
|
+
autoOffTimeout: 60,
|
|
13
|
+
autoReadout: false,
|
|
14
|
+
autoSend: false,
|
|
15
|
+
backupPointer: 256,
|
|
16
|
+
batteryCapacity: 14062,
|
|
17
|
+
batteryDate: new Date('2014-06-11 00:00:00'),
|
|
18
|
+
beeps: false,
|
|
19
|
+
buildDate: new Date('2014-06-11 00:00:00'),
|
|
20
|
+
code: 31,
|
|
21
|
+
deviceModel: 'BSM8',
|
|
22
|
+
extendedProtocol: true,
|
|
23
|
+
firmwareVersion: 3552567,
|
|
24
|
+
flashes: true,
|
|
25
|
+
handshake: true,
|
|
26
|
+
interval: 2621,
|
|
27
|
+
lastWriteDate: new Date('2019-06-20 23:17:13'),
|
|
28
|
+
memoryOverflow: 0,
|
|
29
|
+
memorySize: 128,
|
|
30
|
+
mode: 'Readout',
|
|
31
|
+
passwordOnly: false,
|
|
32
|
+
powerMode: 8,
|
|
33
|
+
program: 48,
|
|
34
|
+
refreshRate: 75,
|
|
35
|
+
serialNumber: 180641,
|
|
36
|
+
siCard6Mode: 193,
|
|
37
|
+
sprint4ms: false,
|
|
38
|
+
stopOnFullBackup: false,
|
|
39
|
+
wtf: 32760
|
|
40
|
+
},
|
|
41
|
+
storageData: [
|
|
42
|
+
...utils.unPrettyHex(`
|
|
43
|
+
00 02 C1 A1 F7 36 35 37 0E 06 0B 91 98 80 20 C0
|
|
44
|
+
4B 08 4E FA 28 0E 06 0B 00 36 EE 80 00 00 18 04
|
|
45
|
+
FF 01 00 00 00 00 00 00 00 00 00 00 4D 70 FF FF
|
|
46
|
+
FF 00 00 C1 00 00 00 0B 00 00 00 00 FF 00 FB E5
|
|
47
|
+
00 24 FC 18 FF FF 19 99 0A 3D 7F F8 85 0C 05 01
|
|
48
|
+
00 00 00 00 FF FF FF FF 00 00 01 0C FF FF FF FF
|
|
49
|
+
30 30 30 35 7D 20 38 00 00 00 00 00 FF FF FF FF
|
|
50
|
+
30 05 1F 33 05 13 06 14 01 9E B9 00 0E 12 00 3C
|
|
51
|
+
`)
|
|
52
|
+
]
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
export const getBSM7Station = utils.cached(cache, () => ({
|
|
56
|
+
stationData: {
|
|
57
|
+
autoOffTimeout: 120,
|
|
58
|
+
autoReadout: false,
|
|
59
|
+
autoSend: false,
|
|
60
|
+
backupPointer: 1032,
|
|
61
|
+
batteryCapacity: 28125,
|
|
62
|
+
batteryDate: new Date('2016-08-04T00:00:00'),
|
|
63
|
+
beeps: true,
|
|
64
|
+
buildDate: new Date('2009-11-31 00:00:00'),
|
|
65
|
+
code: 10,
|
|
66
|
+
deviceModel: 'BSM7',
|
|
67
|
+
extendedProtocol: true,
|
|
68
|
+
firmwareVersion: 3551795,
|
|
69
|
+
flashes: true,
|
|
70
|
+
handshake: true,
|
|
71
|
+
interval: 1310,
|
|
72
|
+
lastWriteDate: new Date('2009-01-01T00:51:55'),
|
|
73
|
+
memoryOverflow: 0,
|
|
74
|
+
memorySize: 128,
|
|
75
|
+
mode: 'Readout',
|
|
76
|
+
passwordOnly: false,
|
|
77
|
+
powerMode: 8,
|
|
78
|
+
program: 36,
|
|
79
|
+
refreshRate: 75,
|
|
80
|
+
serialNumber: 130134,
|
|
81
|
+
siCard6Mode: 0,
|
|
82
|
+
sprint4ms: false,
|
|
83
|
+
stopOnFullBackup: false,
|
|
84
|
+
wtf: 32760
|
|
85
|
+
},
|
|
86
|
+
storageData: [
|
|
87
|
+
...utils.unPrettyHex(`
|
|
88
|
+
00 01 FC 56 F7 36 32 33 09 0C 01 91 97 80 20 DC
|
|
89
|
+
4B 08 4E FA 28 10 08 04 00 6D DD 00 00 00 18 04
|
|
90
|
+
00 04 08 00 00 00 00 00 00 00 00 00 4D 70 FF FF
|
|
91
|
+
01 0D 90 00 00 0C 17 28 00 00 00 00 00 00 F8 FF
|
|
92
|
+
00 24 02 E0 FF 38 19 99 05 1E 7F F8 85 0C 01 01
|
|
93
|
+
85 0C 6B 98 FF FF FF FF FF FF FF FF FF FF FF FF
|
|
94
|
+
30 30 30 35 7D 20 38 00 00 00 00 00 FF FF FF FF
|
|
95
|
+
24 05 0A 35 05 09 01 01 00 0C 2B 00 1C 22 00 78
|
|
96
|
+
`)
|
|
97
|
+
]
|
|
98
|
+
}));
|
|
99
|
+
|
|
100
|
+
export const getSiStationExamples = (): { [name: string]: StationSample } => ({
|
|
101
|
+
BSM8Station: getBSM8Station(),
|
|
102
|
+
BSM7Station: getBSM7Station()
|
|
103
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, expect, test } from '@jest/globals';
|
|
2
|
+
import * as constants from './constants';
|
|
3
|
+
|
|
4
|
+
describe('constants', () => {
|
|
5
|
+
describe('proto', () => {
|
|
6
|
+
test('basicCmd', () => {
|
|
7
|
+
expect('basicCmd' in constants.proto).toBe(true);
|
|
8
|
+
expect('basicCmdLookup' in constants.proto).toBe(true);
|
|
9
|
+
expect(constants.proto.basicCmdLookup).not.toBe(undefined);
|
|
10
|
+
});
|
|
11
|
+
test('cmd', () => {
|
|
12
|
+
expect('cmd' in constants.proto).toBe(true);
|
|
13
|
+
expect('cmdLookup' in constants.proto).toBe(true);
|
|
14
|
+
expect(constants.proto.cmdLookup).not.toBe(undefined);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
});
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { getLookup } from './utils';
|
|
2
|
+
|
|
3
|
+
export const proto = {
|
|
4
|
+
// Protocol characters
|
|
5
|
+
STX: 0x02,
|
|
6
|
+
ETX: 0x03,
|
|
7
|
+
ACK: 0x06, // when sent to BSx3..6, causes beep until SI-card taken out
|
|
8
|
+
NAK: 0x15,
|
|
9
|
+
DLE: 0x10,
|
|
10
|
+
WAKEUP: 0xff,
|
|
11
|
+
|
|
12
|
+
// Basic protocol commands, currently unused
|
|
13
|
+
basicCmd: {
|
|
14
|
+
SET_CARDNO: 0x30,
|
|
15
|
+
GET_SI5: 0x31, // read out SI-card 5 data
|
|
16
|
+
SI5_WRITE: 0x43, // 02 43 (block: 0x30 to 0x37) (16 bytes) 03
|
|
17
|
+
SI5_DET: 0x46, // SI-card 5 inserted (46 49) or removed (46 4F)
|
|
18
|
+
TRANS_REC: 0x53, // autosend timestamp (online control)
|
|
19
|
+
TRANS_TIME: 0x54, // autosend timestamp (lightbeam trigger)
|
|
20
|
+
GET_SI6: 0x61, // read out SI-card 6 data
|
|
21
|
+
SI6_DET: 0x66, // SI-card 6 inserted
|
|
22
|
+
SET_MS: 0x70, // \x4D="M"aster, \x53="S"lave
|
|
23
|
+
GET_MS: 0x71,
|
|
24
|
+
SET_SYS_VAL: 0x72,
|
|
25
|
+
GET_SYS_VAL: 0x73,
|
|
26
|
+
GET_BDATA: 0x74, // Note: response carries '\xC4'!
|
|
27
|
+
ERASE_BDATA: 0x75,
|
|
28
|
+
SET_TIME: 0x76,
|
|
29
|
+
GET_TIME: 0x77,
|
|
30
|
+
OFF: 0x78,
|
|
31
|
+
GET_BDATA2: 0x7a, // Note: response carries '\xCA'!
|
|
32
|
+
SET_BAUD: 0x7e // 0=4800 baud, 1=38400 baud
|
|
33
|
+
},
|
|
34
|
+
get basicCmdLookup(): { [value: number]: string } {
|
|
35
|
+
return getLookup(proto.basicCmd);
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
// Extended protocol commands
|
|
39
|
+
cmd: {
|
|
40
|
+
GET_BACKUP: 0x81,
|
|
41
|
+
SET_SYS_VAL: 0x82,
|
|
42
|
+
GET_SYS_VAL: 0x83,
|
|
43
|
+
SRR_WRITE: 0xa2, // ShortRangeRadio - SysData write
|
|
44
|
+
SRR_READ: 0xa3, // ShortRangeRadio - SysData read
|
|
45
|
+
SRR_QUERY: 0xa6, // ShortRangeRadio - network device query
|
|
46
|
+
SRR_PING: 0xa7, // ShortRangeRadio - heartbeat from linked devices, every 50 seconds
|
|
47
|
+
SRR_ADHOC: 0xa8, // ShortRangeRadio - ad-hoc message, f.ex. from SI-ActiveCard
|
|
48
|
+
GET_SI5: 0xb1, // read out SI-card 5 data
|
|
49
|
+
TRANS_REC: 0xd3, // autosend timestamp (online control)
|
|
50
|
+
CLEAR_CARD: 0xe0, // found on SI-dev-forum: 02 E0 00 E0 00 03 (http://www.sportident.com/en/forum/8/56#59)
|
|
51
|
+
GET_SI6: 0xe1, // read out SI-card 6 data block
|
|
52
|
+
SET_SI6: 0xe2, // write SI-card 6 line (16 bytes)
|
|
53
|
+
SET_SI6_SPECIAL: 0xe4, // write SI-card 6 special fields (e.g. start number)
|
|
54
|
+
SI5_DET: 0xe5, // SI-card 5 inserted
|
|
55
|
+
SI6_DET: 0xe6, // SI-card 6 inserted
|
|
56
|
+
SI_REM: 0xe7, // SI-card removed
|
|
57
|
+
SI8_DET: 0xe8, // SI-card 8/9/10/11/p/t inserted
|
|
58
|
+
SET_SI8: 0xea, // write SI-card 8/9/10/11/p/t data word
|
|
59
|
+
GET_SI8: 0xef, // read out SI-card 8/9/10/11/p/t data block
|
|
60
|
+
SET_MS: 0xf0, // \x4D="M"aster, \x53="S"lave
|
|
61
|
+
GET_MS: 0xf1,
|
|
62
|
+
ERASE_BDATA: 0xf5,
|
|
63
|
+
SET_TIME: 0xf6,
|
|
64
|
+
GET_TIME: 0xf7,
|
|
65
|
+
OFF: 0xf8,
|
|
66
|
+
SIGNAL: 0xf9, // 02 F9 (number of signals) (CRC16) 03
|
|
67
|
+
SET_BAUD: 0xfe // \x00=4800 baud, \x01=38400 baud
|
|
68
|
+
},
|
|
69
|
+
get cmdLookup(): { [value: number]: string } {
|
|
70
|
+
return getLookup(proto.cmd);
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
// If a punch has explicitly no time (e.g. if the start hasn't been punched)
|
|
74
|
+
NO_TIME: 0xeeee,
|
|
75
|
+
|
|
76
|
+
// Protocol Parameters
|
|
77
|
+
P_MS_DIRECT: 0x4d, // "M"aster
|
|
78
|
+
P_MS_REMOTE: 0x53, // "S"lave
|
|
79
|
+
P_SI6_CB: 0x08,
|
|
80
|
+
|
|
81
|
+
// Backup memory record length
|
|
82
|
+
REC_LEN: 8, // Only in extended protocol, otherwise 6!
|
|
83
|
+
|
|
84
|
+
// punch trigger in control mode data structure
|
|
85
|
+
T_OFFSET: 8,
|
|
86
|
+
T_CN: 0,
|
|
87
|
+
T_TIME: 5,
|
|
88
|
+
|
|
89
|
+
// backup memory in control mode
|
|
90
|
+
BC_CN: 3,
|
|
91
|
+
BC_TIME: 8
|
|
92
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, expect, test } from '@jest/globals';
|
|
2
|
+
import * as testUtils from '../../testUtils';
|
|
3
|
+
import { BaseFakeSiCard } from './BaseFakeSiCard';
|
|
4
|
+
|
|
5
|
+
testUtils.useFakeTimers();
|
|
6
|
+
|
|
7
|
+
describe('BaseFakeSiCard', () => {
|
|
8
|
+
test('exists', () => {
|
|
9
|
+
expect(BaseFakeSiCard).not.toBe(undefined);
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type * as storage from '../../storage';
|
|
2
|
+
import type * as siProtocol from '../../siProtocol';
|
|
3
|
+
|
|
4
|
+
export abstract class BaseFakeSiCard {
|
|
5
|
+
public storage: storage.ISiStorage<unknown> = {} as storage.ISiStorage<unknown>;
|
|
6
|
+
|
|
7
|
+
abstract handleDetect(): siProtocol.SiMessage;
|
|
8
|
+
|
|
9
|
+
abstract handleRequest(message: siProtocol.SiMessage): siProtocol.SiMessage[];
|
|
10
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, expect, test } from '@jest/globals';
|
|
2
|
+
import { proto } from '../../../constants';
|
|
3
|
+
import * as utils from '../../../utils';
|
|
4
|
+
import * as testUtils from '../../../testUtils';
|
|
5
|
+
import { FakeModernSiCard } from './FakeModernSiCard';
|
|
6
|
+
import { getEmptyCard } from '../../../SiCard/types/modernSiCardExamples';
|
|
7
|
+
|
|
8
|
+
testUtils.useFakeTimers();
|
|
9
|
+
|
|
10
|
+
describe('FakeModernSiCard', () => {
|
|
11
|
+
test('exists', () => {
|
|
12
|
+
expect(FakeModernSiCard).not.toBe(undefined);
|
|
13
|
+
});
|
|
14
|
+
const testData = getEmptyCard();
|
|
15
|
+
const myFakeModernSiCard = new FakeModernSiCard(testData.storageData);
|
|
16
|
+
test('handleDetect works', () => {
|
|
17
|
+
expect(myFakeModernSiCard.handleDetect()).toEqual({
|
|
18
|
+
command: proto.cmd.SI8_DET,
|
|
19
|
+
parameters: utils.unPrettyHex('00 6B 96 8C')
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
test('handleRequest works', () => {
|
|
23
|
+
expect(() =>
|
|
24
|
+
myFakeModernSiCard.handleRequest({
|
|
25
|
+
command: proto.cmd.GET_SI5,
|
|
26
|
+
parameters: [0x06]
|
|
27
|
+
})
|
|
28
|
+
).toThrow();
|
|
29
|
+
|
|
30
|
+
expect(
|
|
31
|
+
myFakeModernSiCard.handleRequest({
|
|
32
|
+
command: proto.cmd.GET_SI8,
|
|
33
|
+
parameters: [0x06]
|
|
34
|
+
})
|
|
35
|
+
).toEqual([
|
|
36
|
+
{
|
|
37
|
+
command: proto.cmd.GET_SI8,
|
|
38
|
+
parameters: [6, ...testData.storageData.slice(6 * 128, 7 * 128)]
|
|
39
|
+
}
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
expect(
|
|
43
|
+
myFakeModernSiCard.handleRequest({
|
|
44
|
+
command: proto.cmd.GET_SI8,
|
|
45
|
+
parameters: [0x08]
|
|
46
|
+
})
|
|
47
|
+
).toEqual([
|
|
48
|
+
{
|
|
49
|
+
command: proto.cmd.GET_SI8,
|
|
50
|
+
parameters: [0, ...testData.storageData.slice(0, 128)]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
command: proto.cmd.GET_SI8,
|
|
54
|
+
parameters: [6, ...testData.storageData.slice(6 * 128, 7 * 128)]
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
command: proto.cmd.GET_SI8,
|
|
58
|
+
parameters: [7, ...testData.storageData.slice(7 * 128, 8 * 128)]
|
|
59
|
+
}
|
|
60
|
+
]);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import { proto } from '../../../constants';
|
|
3
|
+
import * as siProtocol from '../../../siProtocol';
|
|
4
|
+
import { BaseFakeSiCard } from '../BaseFakeSiCard';
|
|
5
|
+
import { type IModernSiCardStorageFields, ModernSiCard, modernSiCardStorageDefinition } from '../../../SiCard/types/ModernSiCard';
|
|
6
|
+
import { getModernSiCardExamples } from '../../../SiCard/types/modernSiCardExamples';
|
|
7
|
+
import type { ISiStorage } from '../../../storage';
|
|
8
|
+
|
|
9
|
+
export class FakeModernSiCard extends BaseFakeSiCard {
|
|
10
|
+
static siCardClass = ModernSiCard;
|
|
11
|
+
static getAllExamples = getModernSiCardExamples;
|
|
12
|
+
|
|
13
|
+
storage: ISiStorage<IModernSiCardStorageFields>;
|
|
14
|
+
|
|
15
|
+
constructor(storage?: (number | undefined)[]) {
|
|
16
|
+
super();
|
|
17
|
+
this.storage = modernSiCardStorageDefinition(storage);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
handleDetect(): siProtocol.SiMessage {
|
|
21
|
+
const cardNumberArr = siProtocol.cardNumber2arr(this.storage.get('cardNumber')!.value);
|
|
22
|
+
cardNumberArr.reverse();
|
|
23
|
+
return {
|
|
24
|
+
command: proto.cmd.SI8_DET,
|
|
25
|
+
parameters: [...cardNumberArr] as number[]
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
handleRequest(message: siProtocol.SiMessage): siProtocol.SiMessage[] {
|
|
30
|
+
if (message.mode !== undefined || message.command !== proto.cmd.GET_SI8 || message.parameters.length !== 1) {
|
|
31
|
+
throw new Error(`ModernSiCard can not handle ${siProtocol.prettyMessage(message)}`);
|
|
32
|
+
}
|
|
33
|
+
const pageIndex = message.parameters[0];
|
|
34
|
+
const getPageAtIndex = (index: number) => ({
|
|
35
|
+
command: proto.cmd.GET_SI8,
|
|
36
|
+
parameters: [index, ...this.storage.data.slice(index * 128, (index + 1) * 128).toJS()]
|
|
37
|
+
});
|
|
38
|
+
if (pageIndex === 0x08) {
|
|
39
|
+
return [0, 6, 7].map(getPageAtIndex) as siProtocol.SiMessage[];
|
|
40
|
+
}
|
|
41
|
+
return [getPageAtIndex(pageIndex)] as siProtocol.SiMessage[];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type * as storage from '../../../storage';
|
|
2
|
+
import { type IModernSiCardStorageFields, modernSiCardStorageDefinition } from '../../../SiCard/types/ModernSiCard';
|
|
3
|
+
import { FakeModernSiCard } from './FakeModernSiCard';
|
|
4
|
+
import { SIAC } from '../../../SiCard/types/SIAC';
|
|
5
|
+
import { getModernSiCardExamples } from '../../../SiCard/types/modernSiCardExamples';
|
|
6
|
+
|
|
7
|
+
export class FakeSIAC extends FakeModernSiCard {
|
|
8
|
+
static siCardClass = SIAC;
|
|
9
|
+
static getAllExamples = getModernSiCardExamples;
|
|
10
|
+
|
|
11
|
+
public storage: storage.ISiStorage<IModernSiCardStorageFields>;
|
|
12
|
+
|
|
13
|
+
constructor(storageData: (number | undefined)[] | undefined) {
|
|
14
|
+
super();
|
|
15
|
+
this.storage = modernSiCardStorageDefinition(storageData);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type * as storage from '../../../storage';
|
|
2
|
+
import { type IModernSiCardStorageFields, modernSiCardStorageDefinition } from '../../../SiCard/types/ModernSiCard';
|
|
3
|
+
import { FakeModernSiCard } from './FakeModernSiCard';
|
|
4
|
+
import { SiCard10 } from '../../../SiCard/types/SiCard10';
|
|
5
|
+
import { getModernSiCardExamples } from '../../../SiCard/types/modernSiCardExamples';
|
|
6
|
+
|
|
7
|
+
export class FakeSiCard10 extends FakeModernSiCard {
|
|
8
|
+
static siCardClass = SiCard10;
|
|
9
|
+
static getAllExamples = getModernSiCardExamples;
|
|
10
|
+
|
|
11
|
+
public storage: storage.ISiStorage<IModernSiCardStorageFields>;
|
|
12
|
+
|
|
13
|
+
constructor(storageData: (number | undefined)[] | undefined) {
|
|
14
|
+
super();
|
|
15
|
+
this.storage = modernSiCardStorageDefinition(storageData);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type * as storage from '../../../storage';
|
|
2
|
+
import { type IModernSiCardStorageFields, modernSiCardStorageDefinition } from '../../../SiCard/types/ModernSiCard';
|
|
3
|
+
import { FakeModernSiCard } from './FakeModernSiCard';
|
|
4
|
+
import { SiCard11 } from '../../../SiCard/types/SiCard11';
|
|
5
|
+
import { getModernSiCardExamples } from '../../../SiCard/types/modernSiCardExamples';
|
|
6
|
+
|
|
7
|
+
export class FakeSiCard11 extends FakeModernSiCard {
|
|
8
|
+
static siCardClass = SiCard11;
|
|
9
|
+
static getAllExamples = getModernSiCardExamples;
|
|
10
|
+
|
|
11
|
+
public storage: storage.ISiStorage<IModernSiCardStorageFields>;
|
|
12
|
+
|
|
13
|
+
constructor(storageData: (number | undefined)[] | undefined) {
|
|
14
|
+
super();
|
|
15
|
+
this.storage = modernSiCardStorageDefinition(storageData);
|
|
16
|
+
}
|
|
17
|
+
}
|