@rednaxela101/cubing 0.63.5
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/README.md +120 -0
- package/alg/index.d.ts +1 -0
- package/bluetooth/index.d.ts +1 -0
- package/dist/bin/chunks/chunk-22VWCGBC.js +13 -0
- package/dist/bin/chunks/chunk-22VWCGBC.js.map +7 -0
- package/dist/bin/order.js +59 -0
- package/dist/bin/order.js.map +7 -0
- package/dist/bin/puzzle-geometry-bin.js +407 -0
- package/dist/bin/puzzle-geometry-bin.js.map +7 -0
- package/dist/bin/scramble.js +188 -0
- package/dist/bin/scramble.js.map +7 -0
- package/dist/bin/svg.js +56 -0
- package/dist/bin/svg.js.map +7 -0
- package/dist/lib/cubing/alg/index.d.ts +2 -0
- package/dist/lib/cubing/alg/index.js +43 -0
- package/dist/lib/cubing/alg/index.js.map +7 -0
- package/dist/lib/cubing/bluetooth/index.d.ts +189 -0
- package/dist/lib/cubing/bluetooth/index.js +2271 -0
- package/dist/lib/cubing/bluetooth/index.js.map +7 -0
- package/dist/lib/cubing/chunks/big-puzzle-orientation-BA6QHO5C.js +68 -0
- package/dist/lib/cubing/chunks/big-puzzle-orientation-BA6QHO5C.js.map +7 -0
- package/dist/lib/cubing/chunks/chunk-2VJB4TMH.js +1774 -0
- package/dist/lib/cubing/chunks/chunk-2VJB4TMH.js.map +7 -0
- package/dist/lib/cubing/chunks/chunk-52ZXJEUO.js +665 -0
- package/dist/lib/cubing/chunks/chunk-52ZXJEUO.js.map +7 -0
- package/dist/lib/cubing/chunks/chunk-56VZAEPF.js +274 -0
- package/dist/lib/cubing/chunks/chunk-56VZAEPF.js.map +7 -0
- package/dist/lib/cubing/chunks/chunk-7GUL3OBQ.js +9 -0
- package/dist/lib/cubing/chunks/chunk-7GUL3OBQ.js.map +7 -0
- package/dist/lib/cubing/chunks/chunk-7L7BJSIM.js +21 -0
- package/dist/lib/cubing/chunks/chunk-7L7BJSIM.js.map +7 -0
- package/dist/lib/cubing/chunks/chunk-AESPJKN6.js +265 -0
- package/dist/lib/cubing/chunks/chunk-AESPJKN6.js.map +7 -0
- package/dist/lib/cubing/chunks/chunk-C2Q347XS.js +89 -0
- package/dist/lib/cubing/chunks/chunk-C2Q347XS.js.map +7 -0
- package/dist/lib/cubing/chunks/chunk-CGBMJBU3.js +1195 -0
- package/dist/lib/cubing/chunks/chunk-CGBMJBU3.js.map +7 -0
- package/dist/lib/cubing/chunks/chunk-HZQONEMD.js +249 -0
- package/dist/lib/cubing/chunks/chunk-HZQONEMD.js.map +7 -0
- package/dist/lib/cubing/chunks/chunk-IAGCVPL3.js +1069 -0
- package/dist/lib/cubing/chunks/chunk-IAGCVPL3.js.map +7 -0
- package/dist/lib/cubing/chunks/chunk-NAPITA3L.js +1 -0
- package/dist/lib/cubing/chunks/chunk-NAPITA3L.js.map +7 -0
- package/dist/lib/cubing/chunks/chunk-XPDJUSZ7.js +1876 -0
- package/dist/lib/cubing/chunks/chunk-XPDJUSZ7.js.map +7 -0
- package/dist/lib/cubing/chunks/chunk-YSI6PF35.js +619 -0
- package/dist/lib/cubing/chunks/chunk-YSI6PF35.js.map +7 -0
- package/dist/lib/cubing/chunks/inside-LG2EAKCL.js +557 -0
- package/dist/lib/cubing/chunks/inside-LG2EAKCL.js.map +7 -0
- package/dist/lib/cubing/chunks/puzzles-dynamic-2x2x2-JYJJXB4V.js +142 -0
- package/dist/lib/cubing/chunks/puzzles-dynamic-2x2x2-JYJJXB4V.js.map +7 -0
- package/dist/lib/cubing/chunks/puzzles-dynamic-3x3x3-FYXD7SIU.js +365 -0
- package/dist/lib/cubing/chunks/puzzles-dynamic-3x3x3-FYXD7SIU.js.map +7 -0
- package/dist/lib/cubing/chunks/puzzles-dynamic-4x4x4-AIGABOAR.js +132 -0
- package/dist/lib/cubing/chunks/puzzles-dynamic-4x4x4-AIGABOAR.js.map +7 -0
- package/dist/lib/cubing/chunks/puzzles-dynamic-5x5x5-JUNRX27W.js +277 -0
- package/dist/lib/cubing/chunks/puzzles-dynamic-5x5x5-JUNRX27W.js.map +7 -0
- package/dist/lib/cubing/chunks/puzzles-dynamic-clock-USGQTDPO.js +836 -0
- package/dist/lib/cubing/chunks/puzzles-dynamic-clock-USGQTDPO.js.map +7 -0
- package/dist/lib/cubing/chunks/puzzles-dynamic-fto-WJHMLYCJ.js +100 -0
- package/dist/lib/cubing/chunks/puzzles-dynamic-fto-WJHMLYCJ.js.map +7 -0
- package/dist/lib/cubing/chunks/puzzles-dynamic-megaminx-2LVHIDL4.js +224 -0
- package/dist/lib/cubing/chunks/puzzles-dynamic-megaminx-2LVHIDL4.js.map +7 -0
- package/dist/lib/cubing/chunks/puzzles-dynamic-pyraminx-7UWX4HHI.js +93 -0
- package/dist/lib/cubing/chunks/puzzles-dynamic-pyraminx-7UWX4HHI.js.map +7 -0
- package/dist/lib/cubing/chunks/puzzles-dynamic-square1-PQEKBPFN.js +842 -0
- package/dist/lib/cubing/chunks/puzzles-dynamic-square1-PQEKBPFN.js.map +7 -0
- package/dist/lib/cubing/chunks/search-dynamic-sgs-2x2x2-XFKQ4VJS.js +125 -0
- package/dist/lib/cubing/chunks/search-dynamic-sgs-2x2x2-XFKQ4VJS.js.map +7 -0
- package/dist/lib/cubing/chunks/search-dynamic-sgs-megaminx-ZDOOLEDW.js +1564 -0
- package/dist/lib/cubing/chunks/search-dynamic-sgs-megaminx-ZDOOLEDW.js.map +7 -0
- package/dist/lib/cubing/chunks/search-dynamic-sgs-pyraminx-W6UJWBYY.js +108 -0
- package/dist/lib/cubing/chunks/search-dynamic-sgs-pyraminx-W6UJWBYY.js.map +7 -0
- package/dist/lib/cubing/chunks/search-dynamic-sgs-skewb-FXGKV4KD.js +125 -0
- package/dist/lib/cubing/chunks/search-dynamic-sgs-skewb-FXGKV4KD.js.map +7 -0
- package/dist/lib/cubing/chunks/search-dynamic-sgs-unofficial-YK6HA7BK.js +644 -0
- package/dist/lib/cubing/chunks/search-dynamic-sgs-unofficial-YK6HA7BK.js.map +7 -0
- package/dist/lib/cubing/chunks/search-dynamic-solve-3x3x3-DLMAGPXS.js +2549 -0
- package/dist/lib/cubing/chunks/search-dynamic-solve-3x3x3-DLMAGPXS.js.map +7 -0
- package/dist/lib/cubing/chunks/search-dynamic-solve-4x4x4-NONXQHOJ.js +2923 -0
- package/dist/lib/cubing/chunks/search-dynamic-solve-4x4x4-NONXQHOJ.js.map +7 -0
- package/dist/lib/cubing/chunks/search-dynamic-solve-fto-3GQ2HLSW.js +1405 -0
- package/dist/lib/cubing/chunks/search-dynamic-solve-fto-3GQ2HLSW.js.map +7 -0
- package/dist/lib/cubing/chunks/search-worker-entry.js +19 -0
- package/dist/lib/cubing/chunks/search-worker-entry.js.map +7 -0
- package/dist/lib/cubing/chunks/square1-stickerings-W2ZLZWIN.js +73 -0
- package/dist/lib/cubing/chunks/square1-stickerings-W2ZLZWIN.js.map +7 -0
- package/dist/lib/cubing/chunks/twips-2RNTWYNZ.js +433 -0
- package/dist/lib/cubing/chunks/twips-2RNTWYNZ.js.map +7 -0
- package/dist/lib/cubing/chunks/twips_wasm_bg-TJNYGA2C-23XTPLIK.js +10 -0
- package/dist/lib/cubing/chunks/twips_wasm_bg-TJNYGA2C-23XTPLIK.js.map +7 -0
- package/dist/lib/cubing/chunks/twisty-dynamic-3d-JO6OGKOF.js +2424 -0
- package/dist/lib/cubing/chunks/twisty-dynamic-3d-JO6OGKOF.js.map +7 -0
- package/dist/lib/cubing/index-BX6f56uo.d.ts +76 -0
- package/dist/lib/cubing/index-CcRu94bx.d.ts +2622 -0
- package/dist/lib/cubing/kpuzzle/index.d.ts +2 -0
- package/dist/lib/cubing/kpuzzle/index.js +12 -0
- package/dist/lib/cubing/kpuzzle/index.js.map +7 -0
- package/dist/lib/cubing/notation/index.d.ts +32 -0
- package/dist/lib/cubing/notation/index.js +20 -0
- package/dist/lib/cubing/notation/index.js.map +7 -0
- package/dist/lib/cubing/outside-BCzgSYQ_.d.ts +38 -0
- package/dist/lib/cubing/protocol/index.d.ts +28 -0
- package/dist/lib/cubing/protocol/index.js +20 -0
- package/dist/lib/cubing/protocol/index.js.map +7 -0
- package/dist/lib/cubing/puzzle-geometry/index.d.ts +2 -0
- package/dist/lib/cubing/puzzle-geometry/index.js +5024 -0
- package/dist/lib/cubing/puzzle-geometry/index.js.map +7 -0
- package/dist/lib/cubing/puzzles/index.d.ts +2 -0
- package/dist/lib/cubing/puzzles/index.js +23 -0
- package/dist/lib/cubing/puzzles/index.js.map +7 -0
- package/dist/lib/cubing/scramble/index.d.ts +2 -0
- package/dist/lib/cubing/scramble/index.js +16 -0
- package/dist/lib/cubing/scramble/index.js.map +7 -0
- package/dist/lib/cubing/search/index.d.ts +7 -0
- package/dist/lib/cubing/search/index.js +37 -0
- package/dist/lib/cubing/search/index.js.map +7 -0
- package/dist/lib/cubing/stream/index.d.ts +2 -0
- package/dist/lib/cubing/stream/index.js +111 -0
- package/dist/lib/cubing/stream/index.js.map +7 -0
- package/dist/lib/cubing/twisty/index.d.ts +2 -0
- package/dist/lib/cubing/twisty/index.js +6236 -0
- package/dist/lib/cubing/twisty/index.js.map +7 -0
- package/experimental-json-schema/kpuzzle/KPatternData.schema.json +34 -0
- package/experimental-json-schema/kpuzzle/KPuzzleDefinition.schema.json +97 -0
- package/experimental-json-schema/kpuzzle/KTransformationData.schema.json +27 -0
- package/kpuzzle/index.d.ts +1 -0
- package/notation/index.d.ts +1 -0
- package/package.json +146 -0
- package/protocol/index.d.ts +1 -0
- package/puzzle-geometry/index.d.ts +1 -0
- package/puzzles/index.d.ts +1 -0
- package/scramble/index.d.ts +1 -0
- package/search/index.d.ts +1 -0
- package/stream/index.d.ts +1 -0
- package/twisty/index.d.ts +1 -0
|
@@ -0,0 +1,2271 @@
|
|
|
1
|
+
import {
|
|
2
|
+
binaryComponentsToReid3x3x3,
|
|
3
|
+
twizzleBinaryToBinaryComponents
|
|
4
|
+
} from "../chunks/chunk-AESPJKN6.js";
|
|
5
|
+
import {
|
|
6
|
+
puzzles
|
|
7
|
+
} from "../chunks/chunk-YSI6PF35.js";
|
|
8
|
+
import {
|
|
9
|
+
cube3x3x3,
|
|
10
|
+
experimental3x3x3KPuzzle
|
|
11
|
+
} from "../chunks/chunk-2VJB4TMH.js";
|
|
12
|
+
import {
|
|
13
|
+
KPattern
|
|
14
|
+
} from "../chunks/chunk-52ZXJEUO.js";
|
|
15
|
+
import {
|
|
16
|
+
Alg,
|
|
17
|
+
Move,
|
|
18
|
+
experimentalAppendMove,
|
|
19
|
+
keyToMove
|
|
20
|
+
} from "../chunks/chunk-XPDJUSZ7.js";
|
|
21
|
+
|
|
22
|
+
// src/cubing/bluetooth/debug.ts
|
|
23
|
+
var DEBUG_LOGGING_ENABLED = false;
|
|
24
|
+
function enableDebugLogging(enable) {
|
|
25
|
+
DEBUG_LOGGING_ENABLED = enable;
|
|
26
|
+
}
|
|
27
|
+
function debugLog(...args) {
|
|
28
|
+
if (!DEBUG_LOGGING_ENABLED) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (console.info) {
|
|
32
|
+
console.info(...args);
|
|
33
|
+
} else {
|
|
34
|
+
console.log(...args);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/cubing/bluetooth/transformer.ts
|
|
39
|
+
import { Quaternion } from "three/src/math/Quaternion.js";
|
|
40
|
+
import { Vector3 } from "three/src/math/Vector3.js";
|
|
41
|
+
function maxAxis(v) {
|
|
42
|
+
const maxVal = Math.max(Math.abs(v.x), Math.abs(v.y), Math.abs(v.z));
|
|
43
|
+
switch (maxVal) {
|
|
44
|
+
case v.x:
|
|
45
|
+
return "x";
|
|
46
|
+
case -v.x:
|
|
47
|
+
return "-x";
|
|
48
|
+
case v.y:
|
|
49
|
+
return "y";
|
|
50
|
+
case -v.y:
|
|
51
|
+
return "-y";
|
|
52
|
+
case v.z:
|
|
53
|
+
return "z";
|
|
54
|
+
case -v.z:
|
|
55
|
+
return "-z";
|
|
56
|
+
default:
|
|
57
|
+
throw new Error("Uh-oh.");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
var s2 = Math.sqrt(0.5);
|
|
61
|
+
var m = {
|
|
62
|
+
"y z": new Quaternion(0, 0, 0, 1),
|
|
63
|
+
"-z y": new Quaternion(s2, 0, 0, s2),
|
|
64
|
+
"x z": new Quaternion(0, 0, -s2, s2),
|
|
65
|
+
"-x z": new Quaternion(0, 0, s2, s2)
|
|
66
|
+
};
|
|
67
|
+
var BasicRotationTransformer = class {
|
|
68
|
+
// private reorientQuat = new Quaternion();
|
|
69
|
+
transformAlgLeaf(_algLeafEvent) {
|
|
70
|
+
}
|
|
71
|
+
transformOrientation(orientationEvent) {
|
|
72
|
+
const { x, y, z, w } = orientationEvent.quaternion;
|
|
73
|
+
const quat = new Quaternion(x, y, z, w);
|
|
74
|
+
const U = new Vector3(0, 1, 0);
|
|
75
|
+
const F = new Vector3(0, 0, 1);
|
|
76
|
+
const maxU = maxAxis(U.applyQuaternion(quat));
|
|
77
|
+
const maxF = maxAxis(F.applyQuaternion(quat));
|
|
78
|
+
const oriQuat = m[`${maxU} ${maxF}`] || m["y z"];
|
|
79
|
+
console.log(quat);
|
|
80
|
+
console.log(oriQuat);
|
|
81
|
+
const q2 = quat.premultiply(oriQuat);
|
|
82
|
+
console.log(q2);
|
|
83
|
+
orientationEvent.quaternion = quat;
|
|
84
|
+
console.log(orientationEvent.quaternion);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// src/cubing/bluetooth/smart-puzzle/bluetooth-puzzle.ts
|
|
89
|
+
var BluetoothPuzzle = class extends EventTarget {
|
|
90
|
+
transformers = [];
|
|
91
|
+
listeners = [];
|
|
92
|
+
// TODO: type
|
|
93
|
+
orientationListeners = [];
|
|
94
|
+
// TODO: Can we make this reutrn (async) on success?
|
|
95
|
+
// TODO: require subclasses to implement this?
|
|
96
|
+
async getPattern() {
|
|
97
|
+
throw new Error("cannot get pattern");
|
|
98
|
+
}
|
|
99
|
+
addAlgLeafListener(listener) {
|
|
100
|
+
this.listeners.push(listener);
|
|
101
|
+
}
|
|
102
|
+
addOrientationListener(listener) {
|
|
103
|
+
this.orientationListeners.push(listener);
|
|
104
|
+
}
|
|
105
|
+
experimentalAddBasicRotationTransformer() {
|
|
106
|
+
this.transformers.push(new BasicRotationTransformer());
|
|
107
|
+
}
|
|
108
|
+
dispatchAlgLeaf(algLeaf) {
|
|
109
|
+
for (const transformer of this.transformers) {
|
|
110
|
+
transformer.transformAlgLeaf(algLeaf);
|
|
111
|
+
}
|
|
112
|
+
for (const l of this.listeners) {
|
|
113
|
+
l(algLeaf);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
dispatchOrientation(orientationEvent) {
|
|
117
|
+
for (const transformer of this.transformers) {
|
|
118
|
+
transformer.transformOrientation(orientationEvent);
|
|
119
|
+
}
|
|
120
|
+
const { x, y, z, w } = orientationEvent.quaternion;
|
|
121
|
+
orientationEvent.quaternion = {
|
|
122
|
+
x,
|
|
123
|
+
y,
|
|
124
|
+
z,
|
|
125
|
+
w
|
|
126
|
+
};
|
|
127
|
+
for (const l of this.orientationListeners) {
|
|
128
|
+
l(orientationEvent);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// src/cubing/bluetooth/keyboard.ts
|
|
134
|
+
var KeyboardPuzzle = class extends BluetoothPuzzle {
|
|
135
|
+
// (e: KeyboardEvent) => Promise<void>;
|
|
136
|
+
// TODO: Decide on the right arguments.
|
|
137
|
+
// TODO: support tying the puzzle to a TwistyPlayer.
|
|
138
|
+
constructor(target, puzzle = "3x3x3") {
|
|
139
|
+
super();
|
|
140
|
+
this.target = target;
|
|
141
|
+
this.listener = this.onKeyDown.bind(this);
|
|
142
|
+
target.addEventListener("keydown", this.listener);
|
|
143
|
+
this.keyMappingAndPatternPromise = this.setPuzzleInternal(puzzle);
|
|
144
|
+
}
|
|
145
|
+
target;
|
|
146
|
+
keyMappingAndPatternPromise;
|
|
147
|
+
listener;
|
|
148
|
+
name() {
|
|
149
|
+
return "Keyboard Input";
|
|
150
|
+
}
|
|
151
|
+
async setPuzzleInternal(puzzle) {
|
|
152
|
+
const puzzleLoader = await (async () => typeof puzzle === "string" ? (await import("../puzzles/index.js")).puzzles[puzzle] : puzzle)();
|
|
153
|
+
const kpuzzle = await (async () => puzzleLoader.kpuzzle())();
|
|
154
|
+
return Promise.all([puzzleLoader.keyMapping?.(), kpuzzle.defaultPattern()]);
|
|
155
|
+
}
|
|
156
|
+
disconnect() {
|
|
157
|
+
this.target.removeEventListener("keydown", this.listener);
|
|
158
|
+
}
|
|
159
|
+
async getPattern() {
|
|
160
|
+
return (await this.keyMappingAndPatternPromise)[1];
|
|
161
|
+
}
|
|
162
|
+
async onKeyDown(e) {
|
|
163
|
+
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
this.keyMappingAndPatternPromise = (async () => {
|
|
167
|
+
const [keyMapping, pattern] = await this.keyMappingAndPatternPromise;
|
|
168
|
+
const algLeaf = keyToMove(keyMapping, e);
|
|
169
|
+
let newPattern;
|
|
170
|
+
if (algLeaf) {
|
|
171
|
+
newPattern = pattern.applyAlg(new Alg([algLeaf]));
|
|
172
|
+
this.dispatchAlgLeaf({
|
|
173
|
+
latestAlgLeaf: algLeaf,
|
|
174
|
+
timeStamp: e.timeStamp,
|
|
175
|
+
pattern: newPattern
|
|
176
|
+
});
|
|
177
|
+
e.preventDefault();
|
|
178
|
+
}
|
|
179
|
+
return [keyMapping, newPattern ?? pattern];
|
|
180
|
+
})();
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
async function debugKeyboardConnect(target = globalThis, puzzle = "3x3x3") {
|
|
184
|
+
return new KeyboardPuzzle(target, puzzle);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/cubing/bluetooth/connect/index.ts
|
|
188
|
+
function requestOptions(configs, acceptAllDevices = false) {
|
|
189
|
+
const options = acceptAllDevices ? {
|
|
190
|
+
acceptAllDevices: true,
|
|
191
|
+
optionalServices: []
|
|
192
|
+
} : {
|
|
193
|
+
filters: [],
|
|
194
|
+
optionalServices: []
|
|
195
|
+
};
|
|
196
|
+
for (const config of configs) {
|
|
197
|
+
if (!acceptAllDevices) {
|
|
198
|
+
options.filters = options.filters.concat(config.filters);
|
|
199
|
+
}
|
|
200
|
+
options.optionalServices = options.optionalServices.concat(
|
|
201
|
+
config.optionalServices
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
debugLog({ requestOptions: options });
|
|
205
|
+
return options;
|
|
206
|
+
}
|
|
207
|
+
var consecutiveFailures = 0;
|
|
208
|
+
var MAX_FAILURES_BEFORE_ACCEPT_ALL_FALLBACK = 2;
|
|
209
|
+
async function bluetoothConnect(configs, options = {}) {
|
|
210
|
+
debugLog("Attempting to pair.");
|
|
211
|
+
let device;
|
|
212
|
+
try {
|
|
213
|
+
let acceptAllDevices = options.acceptAllDevices;
|
|
214
|
+
if (!acceptAllDevices && consecutiveFailures >= MAX_FAILURES_BEFORE_ACCEPT_ALL_FALLBACK) {
|
|
215
|
+
console.info(
|
|
216
|
+
`The last ${MAX_FAILURES_BEFORE_ACCEPT_ALL_FALLBACK} Bluetooth puzzle connection attempts failed. This time, the Bluetooth prompt will show all possible devices.`
|
|
217
|
+
);
|
|
218
|
+
acceptAllDevices = true;
|
|
219
|
+
}
|
|
220
|
+
device = await navigator.bluetooth.requestDevice(
|
|
221
|
+
requestOptions(configs, acceptAllDevices)
|
|
222
|
+
);
|
|
223
|
+
consecutiveFailures = 0;
|
|
224
|
+
} catch (e) {
|
|
225
|
+
consecutiveFailures++;
|
|
226
|
+
throw e;
|
|
227
|
+
}
|
|
228
|
+
debugLog("Device:", device);
|
|
229
|
+
if (typeof device.gatt === "undefined") {
|
|
230
|
+
return Promise.reject("Device did not have a GATT server.");
|
|
231
|
+
}
|
|
232
|
+
const server = await device.gatt.connect();
|
|
233
|
+
debugLog("Server:", server);
|
|
234
|
+
const name = server.device?.name || "";
|
|
235
|
+
for (const config of configs) {
|
|
236
|
+
for (const prefix of config.prefixes) {
|
|
237
|
+
if (name?.startsWith(prefix)) {
|
|
238
|
+
return config.connect(server, device);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
throw Error("Unknown Bluetooth devive.");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// src/cubing/bluetooth/smart-puzzle/gan.ts
|
|
246
|
+
import { Quaternion as Quaternion2 } from "three/src/math/Quaternion.js";
|
|
247
|
+
|
|
248
|
+
// src/cubing/vendor/public-domain/unsafe-raw-aes/unsafe-raw-aes.ts
|
|
249
|
+
var blockSize = 16;
|
|
250
|
+
var zeros = new Uint8Array(blockSize);
|
|
251
|
+
var paddingBlockPlaintext = new Uint8Array(
|
|
252
|
+
new Array(blockSize).fill(blockSize)
|
|
253
|
+
);
|
|
254
|
+
var AES_CBC = "AES-CBC";
|
|
255
|
+
async function importKey(keyBytes) {
|
|
256
|
+
return await crypto.subtle.importKey(
|
|
257
|
+
"raw",
|
|
258
|
+
keyBytes,
|
|
259
|
+
AES_CBC,
|
|
260
|
+
true,
|
|
261
|
+
["encrypt", "decrypt"]
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
async function unsafeEncryptBlockWithIV(key, plaintextBlock, iv) {
|
|
265
|
+
const cryptoResult = await globalThis.crypto.subtle.encrypt(
|
|
266
|
+
{
|
|
267
|
+
name: AES_CBC,
|
|
268
|
+
iv
|
|
269
|
+
},
|
|
270
|
+
key,
|
|
271
|
+
plaintextBlock
|
|
272
|
+
);
|
|
273
|
+
return cryptoResult.slice(0, blockSize);
|
|
274
|
+
}
|
|
275
|
+
async function unsafeEncryptBlock(key, plaintextBlock) {
|
|
276
|
+
return (await unsafeEncryptBlockWithIV(key, plaintextBlock, zeros)).slice(
|
|
277
|
+
0,
|
|
278
|
+
blockSize
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
async function unsafeDecryptBlock(key, ciphertextBlock) {
|
|
282
|
+
const paddingBlock = await unsafeEncryptBlockWithIV(
|
|
283
|
+
key,
|
|
284
|
+
paddingBlockPlaintext,
|
|
285
|
+
ciphertextBlock
|
|
286
|
+
);
|
|
287
|
+
const cbcCiphertext = new Uint8Array(2 * blockSize);
|
|
288
|
+
cbcCiphertext.set(new Uint8Array(ciphertextBlock), 0);
|
|
289
|
+
cbcCiphertext.set(new Uint8Array(paddingBlock), blockSize);
|
|
290
|
+
const cryptoResult = await globalThis.crypto.subtle.decrypt(
|
|
291
|
+
{
|
|
292
|
+
name: AES_CBC,
|
|
293
|
+
iv: zeros
|
|
294
|
+
},
|
|
295
|
+
key,
|
|
296
|
+
cbcCiphertext
|
|
297
|
+
);
|
|
298
|
+
return cryptoResult.slice(0, blockSize);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// src/cubing/bluetooth/smart-puzzle/common.ts
|
|
302
|
+
var reidEdgeOrder = "UF UR UB UL DF DR DB DL FR FL BR BL".split(" ");
|
|
303
|
+
var reidCornerOrder = "UFR URB UBL ULF DRF DFL DLB DBR".split(" ");
|
|
304
|
+
function rotateLeft(s, i) {
|
|
305
|
+
return s.slice(i) + s.slice(0, i);
|
|
306
|
+
}
|
|
307
|
+
var pieceMap = {};
|
|
308
|
+
reidEdgeOrder.forEach((edge, idx) => {
|
|
309
|
+
for (let i = 0; i < 2; i++) {
|
|
310
|
+
pieceMap[rotateLeft(edge, i)] = { piece: idx, orientation: i };
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
reidCornerOrder.forEach((corner, idx) => {
|
|
314
|
+
for (let i = 0; i < 3; i++) {
|
|
315
|
+
pieceMap[rotateLeft(corner, i)] = { piece: idx, orientation: i };
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
function getPatternData(stickers, faceOrder3, edgeMappings, cornerMappings) {
|
|
319
|
+
const patternData = {
|
|
320
|
+
CORNERS: {
|
|
321
|
+
pieces: [],
|
|
322
|
+
orientation: []
|
|
323
|
+
},
|
|
324
|
+
EDGES: {
|
|
325
|
+
pieces: [],
|
|
326
|
+
orientation: []
|
|
327
|
+
},
|
|
328
|
+
CENTERS: {
|
|
329
|
+
pieces: [0, 1, 2, 3, 4, 5],
|
|
330
|
+
orientation: [0, 0, 0, 0, 0, 0],
|
|
331
|
+
orientationMod: [1, 1, 1, 1, 1, 1]
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
for (const cornerMapping of cornerMappings) {
|
|
335
|
+
const pieceInfo = pieceMap[cornerMapping.map((i) => faceOrder3[stickers[i]]).join("")];
|
|
336
|
+
patternData["CORNERS"].pieces.push(pieceInfo.piece);
|
|
337
|
+
patternData["CORNERS"].orientation.push(pieceInfo.orientation);
|
|
338
|
+
}
|
|
339
|
+
for (const edgeMapping of edgeMappings) {
|
|
340
|
+
const pieceInfo = pieceMap[edgeMapping.map((i) => faceOrder3[stickers[i]]).join("")];
|
|
341
|
+
patternData["EDGES"].pieces.push(pieceInfo.piece);
|
|
342
|
+
patternData["EDGES"].orientation.push(pieceInfo.orientation);
|
|
343
|
+
}
|
|
344
|
+
return patternData;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// src/cubing/bluetooth/smart-puzzle/gan.ts
|
|
348
|
+
var DEFAULT_INTERVAL_MS = 150;
|
|
349
|
+
var MAX_LATEST_MOVES = 6;
|
|
350
|
+
var ganMoveToBlockMove = {
|
|
351
|
+
0: new Move("U"),
|
|
352
|
+
2: new Move("U", -1),
|
|
353
|
+
3: new Move("R"),
|
|
354
|
+
5: new Move("R", -1),
|
|
355
|
+
6: new Move("F"),
|
|
356
|
+
8: new Move("F", -1),
|
|
357
|
+
9: new Move("D"),
|
|
358
|
+
11: new Move("D", -1),
|
|
359
|
+
12: new Move("L"),
|
|
360
|
+
14: new Move("L", -1),
|
|
361
|
+
15: new Move("B"),
|
|
362
|
+
17: new Move("B", -1)
|
|
363
|
+
};
|
|
364
|
+
var homeQuatInverse = null;
|
|
365
|
+
function probablyDecodedCorrectly(data) {
|
|
366
|
+
return data[13] < 18 && data[14] < 18 && data[15] < 18 && data[16] < 18 && data[17] < 18 && data[18] < 18;
|
|
367
|
+
}
|
|
368
|
+
var key10 = new Uint8Array([
|
|
369
|
+
198,
|
|
370
|
+
202,
|
|
371
|
+
21,
|
|
372
|
+
223,
|
|
373
|
+
79,
|
|
374
|
+
110,
|
|
375
|
+
19,
|
|
376
|
+
182,
|
|
377
|
+
119,
|
|
378
|
+
13,
|
|
379
|
+
230,
|
|
380
|
+
89,
|
|
381
|
+
58,
|
|
382
|
+
175,
|
|
383
|
+
186,
|
|
384
|
+
162
|
|
385
|
+
]);
|
|
386
|
+
var key11 = new Uint8Array([
|
|
387
|
+
67,
|
|
388
|
+
226,
|
|
389
|
+
91,
|
|
390
|
+
214,
|
|
391
|
+
125,
|
|
392
|
+
220,
|
|
393
|
+
120,
|
|
394
|
+
216,
|
|
395
|
+
7,
|
|
396
|
+
96,
|
|
397
|
+
163,
|
|
398
|
+
218,
|
|
399
|
+
130,
|
|
400
|
+
60,
|
|
401
|
+
1,
|
|
402
|
+
241
|
|
403
|
+
]);
|
|
404
|
+
async function decryptPattern(data, aesKey) {
|
|
405
|
+
if (aesKey === null) {
|
|
406
|
+
return data;
|
|
407
|
+
}
|
|
408
|
+
const copy = new Uint8Array(data);
|
|
409
|
+
copy.set(new Uint8Array(await unsafeDecryptBlock(aesKey, copy.slice(3))), 3);
|
|
410
|
+
copy.set(
|
|
411
|
+
new Uint8Array(await unsafeDecryptBlock(aesKey, copy.slice(0, 16))),
|
|
412
|
+
0
|
|
413
|
+
);
|
|
414
|
+
if (probablyDecodedCorrectly(copy)) {
|
|
415
|
+
return copy;
|
|
416
|
+
}
|
|
417
|
+
throw new Error("Invalid Gan cube pattern");
|
|
418
|
+
}
|
|
419
|
+
var PhysicalState = class _PhysicalState {
|
|
420
|
+
constructor(dataView, timeStamp) {
|
|
421
|
+
this.dataView = dataView;
|
|
422
|
+
this.timeStamp = timeStamp;
|
|
423
|
+
this.arr = new Uint8Array(dataView.buffer);
|
|
424
|
+
if (this.arr.length !== this.arrLen) {
|
|
425
|
+
throw new Error("Unexpected array length");
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
dataView;
|
|
429
|
+
timeStamp;
|
|
430
|
+
static async read(characteristic, aesKey) {
|
|
431
|
+
const value = await decryptPattern(
|
|
432
|
+
new Uint8Array((await characteristic.readValue()).buffer),
|
|
433
|
+
aesKey
|
|
434
|
+
);
|
|
435
|
+
const timeStamp = Date.now();
|
|
436
|
+
return new _PhysicalState(new DataView(value.buffer), timeStamp);
|
|
437
|
+
}
|
|
438
|
+
arr;
|
|
439
|
+
arrLen = 19;
|
|
440
|
+
rotQuat() {
|
|
441
|
+
let x = this.dataView.getInt16(0, true) / 16384;
|
|
442
|
+
let y = this.dataView.getInt16(2, true) / 16384;
|
|
443
|
+
let z = this.dataView.getInt16(4, true) / 16384;
|
|
444
|
+
[x, y, z] = [-y, z, -x];
|
|
445
|
+
const wSquared = 1 - (x * x + y * y + z * z);
|
|
446
|
+
const w = wSquared > 0 ? Math.sqrt(wSquared) : 0;
|
|
447
|
+
const quat = new Quaternion2(x, y, z, w);
|
|
448
|
+
if (!homeQuatInverse) {
|
|
449
|
+
homeQuatInverse = quat.clone().invert();
|
|
450
|
+
}
|
|
451
|
+
return quat.clone().multiply(homeQuatInverse.clone());
|
|
452
|
+
}
|
|
453
|
+
// Loops from 255 to 0.
|
|
454
|
+
moveCounter() {
|
|
455
|
+
return this.arr[12];
|
|
456
|
+
}
|
|
457
|
+
numMovesSince(previousMoveCounter) {
|
|
458
|
+
return this.moveCounter() - previousMoveCounter & 255;
|
|
459
|
+
}
|
|
460
|
+
// Due to the design of the Gan356i protocol, it's common to query for the
|
|
461
|
+
// latest physical state and find 0 moves have been performed since the last
|
|
462
|
+
// query. Therefore, it's useful to allow 0 as an argument.
|
|
463
|
+
latestMoves(n) {
|
|
464
|
+
if (n < 0 || n > MAX_LATEST_MOVES) {
|
|
465
|
+
throw new Error(`Must ask for 0 to 6 latest moves. (Asked for ${n})`);
|
|
466
|
+
}
|
|
467
|
+
return Array.from(this.arr.slice(19 - n, 19)).map(
|
|
468
|
+
(i) => ganMoveToBlockMove[i]
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
debugInfo() {
|
|
472
|
+
return {
|
|
473
|
+
arr: this.arr
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
var UUIDs = {
|
|
478
|
+
ganCubeService: "0000fff0-0000-1000-8000-00805f9b34fb",
|
|
479
|
+
physicalStateCharacteristic: "0000fff5-0000-1000-8000-00805f9b34fb",
|
|
480
|
+
actualAngleAndBatteryCharacteristic: "0000fff7-0000-1000-8000-00805f9b34fb",
|
|
481
|
+
faceletStatus1Characteristic: "0000fff2-0000-1000-8000-00805f9b34fb",
|
|
482
|
+
faceletStatus2Characteristic: "0000fff3-0000-1000-8000-00805f9b34fb",
|
|
483
|
+
infoService: "0000180a-0000-1000-8000-00805f9b34fb",
|
|
484
|
+
systemIDCharacteristic: "00002a23-0000-1000-8000-00805f9b34fb",
|
|
485
|
+
versionCharacteristic: "00002a28-0000-1000-8000-00805f9b34fb"
|
|
486
|
+
};
|
|
487
|
+
var commands = {
|
|
488
|
+
reset: new Uint8Array([
|
|
489
|
+
0,
|
|
490
|
+
0,
|
|
491
|
+
36,
|
|
492
|
+
0,
|
|
493
|
+
73,
|
|
494
|
+
146,
|
|
495
|
+
36,
|
|
496
|
+
73,
|
|
497
|
+
109,
|
|
498
|
+
146,
|
|
499
|
+
219,
|
|
500
|
+
182,
|
|
501
|
+
73,
|
|
502
|
+
146,
|
|
503
|
+
182,
|
|
504
|
+
36,
|
|
505
|
+
109,
|
|
506
|
+
219
|
|
507
|
+
])
|
|
508
|
+
};
|
|
509
|
+
function buf2hex(buffer) {
|
|
510
|
+
return Array.prototype.map.call(
|
|
511
|
+
new Uint8Array(buffer),
|
|
512
|
+
(x) => `00${x.toString(16)}`.slice(-2)
|
|
513
|
+
).join(" ");
|
|
514
|
+
}
|
|
515
|
+
var gan356iCornerMappings = [
|
|
516
|
+
[0, 21, 15],
|
|
517
|
+
[5, 13, 47],
|
|
518
|
+
[7, 45, 39],
|
|
519
|
+
[2, 37, 23],
|
|
520
|
+
[29, 10, 16],
|
|
521
|
+
[31, 18, 32],
|
|
522
|
+
[26, 34, 40],
|
|
523
|
+
[24, 42, 8]
|
|
524
|
+
];
|
|
525
|
+
var gan356iEdgeMappings = [
|
|
526
|
+
[1, 22],
|
|
527
|
+
[3, 14],
|
|
528
|
+
[6, 46],
|
|
529
|
+
[4, 38],
|
|
530
|
+
[30, 17],
|
|
531
|
+
[27, 9],
|
|
532
|
+
[25, 41],
|
|
533
|
+
[28, 33],
|
|
534
|
+
[19, 12],
|
|
535
|
+
[20, 35],
|
|
536
|
+
[44, 11],
|
|
537
|
+
[43, 36]
|
|
538
|
+
];
|
|
539
|
+
var faceOrder = "URFDLB";
|
|
540
|
+
async function getKey(server) {
|
|
541
|
+
const infoService = await server.getPrimaryService(UUIDs.infoService);
|
|
542
|
+
const versionCharacteristic = await infoService.getCharacteristic(
|
|
543
|
+
UUIDs.versionCharacteristic
|
|
544
|
+
);
|
|
545
|
+
const versionBuffer = new Uint8Array(
|
|
546
|
+
(await versionCharacteristic.readValue()).buffer
|
|
547
|
+
);
|
|
548
|
+
const versionValue = ((versionBuffer[0] << 8) + versionBuffer[1] << 8) + versionBuffer[2];
|
|
549
|
+
if (versionValue < 65544) {
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
const keyXor = versionValue < 65792 ? key10 : key11;
|
|
553
|
+
const systemIDCharacteristic = await infoService.getCharacteristic(
|
|
554
|
+
UUIDs.systemIDCharacteristic
|
|
555
|
+
);
|
|
556
|
+
const systemID = new Uint8Array(
|
|
557
|
+
(await systemIDCharacteristic.readValue()).buffer
|
|
558
|
+
).reverse();
|
|
559
|
+
const key = new Uint8Array(keyXor);
|
|
560
|
+
for (let i = 0; i < systemID.length; i++) {
|
|
561
|
+
key[i] = (key[i] + systemID[i]) % 256;
|
|
562
|
+
}
|
|
563
|
+
return importKey(key);
|
|
564
|
+
}
|
|
565
|
+
var GanCube = class _GanCube extends BluetoothPuzzle {
|
|
566
|
+
constructor(kpuzzle, service, server, physicalStateCharacteristic, lastMoveCounter, aesKey) {
|
|
567
|
+
super();
|
|
568
|
+
this.kpuzzle = kpuzzle;
|
|
569
|
+
this.service = service;
|
|
570
|
+
this.server = server;
|
|
571
|
+
this.physicalStateCharacteristic = physicalStateCharacteristic;
|
|
572
|
+
this.lastMoveCounter = lastMoveCounter;
|
|
573
|
+
this.aesKey = aesKey;
|
|
574
|
+
this.pattern = kpuzzle.defaultPattern();
|
|
575
|
+
this.startTrackingMoves();
|
|
576
|
+
}
|
|
577
|
+
kpuzzle;
|
|
578
|
+
service;
|
|
579
|
+
server;
|
|
580
|
+
physicalStateCharacteristic;
|
|
581
|
+
lastMoveCounter;
|
|
582
|
+
aesKey;
|
|
583
|
+
// We have to perform async operations before we call the constructor.
|
|
584
|
+
static async connect(server) {
|
|
585
|
+
const ganCubeService = await server.getPrimaryService(UUIDs.ganCubeService);
|
|
586
|
+
debugLog("Service:", ganCubeService);
|
|
587
|
+
const physicalStateCharacteristic = await ganCubeService.getCharacteristic(
|
|
588
|
+
UUIDs.physicalStateCharacteristic
|
|
589
|
+
);
|
|
590
|
+
debugLog("Characteristic:", physicalStateCharacteristic);
|
|
591
|
+
const aesKey = await getKey(server);
|
|
592
|
+
const initialMoveCounter = (await PhysicalState.read(physicalStateCharacteristic, aesKey)).moveCounter();
|
|
593
|
+
debugLog("Initial Move Counter:", initialMoveCounter);
|
|
594
|
+
const cube = new _GanCube(
|
|
595
|
+
await puzzles["3x3x3"].kpuzzle(),
|
|
596
|
+
ganCubeService,
|
|
597
|
+
server,
|
|
598
|
+
physicalStateCharacteristic,
|
|
599
|
+
initialMoveCounter,
|
|
600
|
+
aesKey
|
|
601
|
+
);
|
|
602
|
+
return cube;
|
|
603
|
+
}
|
|
604
|
+
INTERVAL_MS = DEFAULT_INTERVAL_MS;
|
|
605
|
+
intervalHandle = null;
|
|
606
|
+
pattern;
|
|
607
|
+
cachedFaceletStatus1Characteristic;
|
|
608
|
+
cachedFaceletStatus2Characteristic;
|
|
609
|
+
cachedActualAngleAndBatteryCharacteristic;
|
|
610
|
+
name() {
|
|
611
|
+
return this.server.device.name;
|
|
612
|
+
}
|
|
613
|
+
disconnect() {
|
|
614
|
+
this.server.disconnect();
|
|
615
|
+
}
|
|
616
|
+
startTrackingMoves() {
|
|
617
|
+
this.intervalHandle = window.setInterval(
|
|
618
|
+
this.intervalHandler.bind(this),
|
|
619
|
+
this.INTERVAL_MS
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
stopTrackingMoves() {
|
|
623
|
+
if (!this.intervalHandle) {
|
|
624
|
+
throw new Error("Not tracking moves!");
|
|
625
|
+
}
|
|
626
|
+
clearInterval(this.intervalHandle);
|
|
627
|
+
this.intervalHandle = null;
|
|
628
|
+
}
|
|
629
|
+
// TODO: Can we ever receive async responses out of order?
|
|
630
|
+
async intervalHandler() {
|
|
631
|
+
const physicalState = await PhysicalState.read(
|
|
632
|
+
this.physicalStateCharacteristic,
|
|
633
|
+
this.aesKey
|
|
634
|
+
);
|
|
635
|
+
let numInterveningMoves = physicalState.numMovesSince(this.lastMoveCounter);
|
|
636
|
+
if (numInterveningMoves > MAX_LATEST_MOVES) {
|
|
637
|
+
debugLog(
|
|
638
|
+
`Too many moves! Dropping ${numInterveningMoves - MAX_LATEST_MOVES} moves`
|
|
639
|
+
);
|
|
640
|
+
numInterveningMoves = MAX_LATEST_MOVES;
|
|
641
|
+
}
|
|
642
|
+
for (const move of physicalState.latestMoves(numInterveningMoves)) {
|
|
643
|
+
this.pattern = this.pattern.applyMove(move);
|
|
644
|
+
this.dispatchAlgLeaf({
|
|
645
|
+
latestAlgLeaf: move,
|
|
646
|
+
timeStamp: physicalState.timeStamp,
|
|
647
|
+
debug: physicalState.debugInfo(),
|
|
648
|
+
pattern: this.pattern
|
|
649
|
+
// quaternion: physicalState.rotQuat(),
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
this.dispatchOrientation({
|
|
653
|
+
timeStamp: physicalState.timeStamp,
|
|
654
|
+
quaternion: physicalState.rotQuat()
|
|
655
|
+
});
|
|
656
|
+
this.lastMoveCounter = physicalState.moveCounter();
|
|
657
|
+
}
|
|
658
|
+
async getBattery() {
|
|
659
|
+
return new Uint8Array(
|
|
660
|
+
await this.readActualAngleAndBatteryCharacteristic()
|
|
661
|
+
)[7];
|
|
662
|
+
}
|
|
663
|
+
async getPattern() {
|
|
664
|
+
const arr = await decryptPattern(
|
|
665
|
+
new Uint8Array(await this.readFaceletStatus1Characteristic()),
|
|
666
|
+
this.aesKey
|
|
667
|
+
);
|
|
668
|
+
const stickers = [];
|
|
669
|
+
for (let i = 0; i < 18; i += 3) {
|
|
670
|
+
let v = ((arr[i ^ 1] << 8) + arr[i + 1 ^ 1] << 8) + arr[i + 2 ^ 1];
|
|
671
|
+
for (let j = 0; j < 8; j++) {
|
|
672
|
+
stickers.push(v & 7);
|
|
673
|
+
v >>= 3;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return new KPattern(
|
|
677
|
+
this.kpuzzle,
|
|
678
|
+
getPatternData(
|
|
679
|
+
stickers,
|
|
680
|
+
faceOrder,
|
|
681
|
+
gan356iEdgeMappings,
|
|
682
|
+
gan356iCornerMappings
|
|
683
|
+
)
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
async faceletStatus1Characteristic() {
|
|
687
|
+
this.cachedFaceletStatus1Characteristic = this.cachedFaceletStatus1Characteristic || this.service.getCharacteristic(UUIDs.faceletStatus1Characteristic);
|
|
688
|
+
return this.cachedFaceletStatus1Characteristic;
|
|
689
|
+
}
|
|
690
|
+
async faceletStatus2Characteristic() {
|
|
691
|
+
this.cachedFaceletStatus2Characteristic = this.cachedFaceletStatus2Characteristic || this.service.getCharacteristic(UUIDs.faceletStatus2Characteristic);
|
|
692
|
+
return this.cachedFaceletStatus2Characteristic;
|
|
693
|
+
}
|
|
694
|
+
async actualAngleAndBatteryCharacteristic() {
|
|
695
|
+
this.cachedActualAngleAndBatteryCharacteristic = this.cachedActualAngleAndBatteryCharacteristic || this.service.getCharacteristic(UUIDs.actualAngleAndBatteryCharacteristic);
|
|
696
|
+
return this.cachedActualAngleAndBatteryCharacteristic;
|
|
697
|
+
}
|
|
698
|
+
async reset() {
|
|
699
|
+
const faceletStatus1Characteristic = await this.faceletStatus1Characteristic();
|
|
700
|
+
await faceletStatus1Characteristic.writeValue(commands["reset"]);
|
|
701
|
+
}
|
|
702
|
+
async readFaceletStatus1Characteristic() {
|
|
703
|
+
const faceletStatus1Characteristic = await this.faceletStatus1Characteristic();
|
|
704
|
+
return (await faceletStatus1Characteristic.readValue()).buffer;
|
|
705
|
+
}
|
|
706
|
+
async readFaceletStatus2Characteristic() {
|
|
707
|
+
const faceletStatus2Characteristic = await this.faceletStatus2Characteristic();
|
|
708
|
+
return buf2hex((await faceletStatus2Characteristic.readValue()).buffer);
|
|
709
|
+
}
|
|
710
|
+
async readActualAngleAndBatteryCharacteristic() {
|
|
711
|
+
const actualAngleAndBatteryCharacteristic = await this.actualAngleAndBatteryCharacteristic();
|
|
712
|
+
return (await actualAngleAndBatteryCharacteristic.readValue()).buffer;
|
|
713
|
+
}
|
|
714
|
+
// TODO
|
|
715
|
+
// private onphysicalStateCharacteristicChanged(event: any): void {
|
|
716
|
+
// var val = event.target.value;
|
|
717
|
+
// debugLog(val);
|
|
718
|
+
// }
|
|
719
|
+
};
|
|
720
|
+
var ganConfig = {
|
|
721
|
+
connect: GanCube.connect.bind(GanCube),
|
|
722
|
+
prefixes: ["GAN"],
|
|
723
|
+
filters: [{ namePrefix: "GAN" }],
|
|
724
|
+
optionalServices: [UUIDs.ganCubeService, UUIDs.infoService]
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
// src/cubing/bluetooth/smart-puzzle/giiker.ts
|
|
728
|
+
var MESSAGE_LENGTH = 20;
|
|
729
|
+
var UUIDs2 = {
|
|
730
|
+
cubeService: "0000aadb-0000-1000-8000-00805f9b34fb",
|
|
731
|
+
cubeCharacteristic: "0000aadc-0000-1000-8000-00805f9b34fb"
|
|
732
|
+
};
|
|
733
|
+
function giikerMoveToAlgMove(face, amount) {
|
|
734
|
+
switch (amount) {
|
|
735
|
+
case 3: {
|
|
736
|
+
amount = -1;
|
|
737
|
+
break;
|
|
738
|
+
}
|
|
739
|
+
case 9: {
|
|
740
|
+
debugLog("Encountered 9", face, amount);
|
|
741
|
+
amount = -2;
|
|
742
|
+
break;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
const family = ["?", "B", "D", "L", "U", "R", "F"][face];
|
|
746
|
+
return new Move(family, amount);
|
|
747
|
+
}
|
|
748
|
+
function giikerStateStr(giikerState) {
|
|
749
|
+
let str = "";
|
|
750
|
+
str += giikerState.slice(0, 8).join(".");
|
|
751
|
+
str += "\n";
|
|
752
|
+
str += giikerState.slice(8, 16).join(".");
|
|
753
|
+
str += "\n";
|
|
754
|
+
str += giikerState.slice(16, 28).join(".");
|
|
755
|
+
str += "\n";
|
|
756
|
+
str += giikerState.slice(28, 32).join(".");
|
|
757
|
+
str += "\n";
|
|
758
|
+
str += giikerState.slice(32, 40).join(".");
|
|
759
|
+
return str;
|
|
760
|
+
}
|
|
761
|
+
var Reid333SolvedCenters = {
|
|
762
|
+
pieces: [0, 1, 2, 3, 4, 5],
|
|
763
|
+
orientation: [0, 0, 0, 0, 0, 0],
|
|
764
|
+
orientationMod: [1, 1, 1, 1, 1, 1]
|
|
765
|
+
// TODO
|
|
766
|
+
};
|
|
767
|
+
var epGiiKERtoReid333 = [4, 8, 0, 9, 5, 1, 3, 7, 6, 10, 2, 11];
|
|
768
|
+
var epReid333toGiiKER = [2, 5, 10, 6, 0, 4, 8, 7, 1, 3, 9, 11];
|
|
769
|
+
var preEO = [1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0];
|
|
770
|
+
var postEO = [1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0];
|
|
771
|
+
var cpGiiKERtoReid333 = [4, 0, 3, 5, 7, 1, 2, 6];
|
|
772
|
+
var cpReid333toGiiKER = [1, 5, 6, 2, 0, 3, 7, 4];
|
|
773
|
+
var preCO = [1, 2, 1, 2, 2, 1, 2, 1];
|
|
774
|
+
var postCO = [2, 1, 2, 1, 1, 2, 1, 2];
|
|
775
|
+
var coFlip = [-1, 1, -1, 1, 1, -1, 1, -1];
|
|
776
|
+
function getNibble(val, i) {
|
|
777
|
+
if (i % 2 === 1) {
|
|
778
|
+
return val[i / 2 | 0] % 16;
|
|
779
|
+
}
|
|
780
|
+
return 0 | val[i / 2 | 0] / 16;
|
|
781
|
+
}
|
|
782
|
+
function probablyEncrypted(data) {
|
|
783
|
+
return data[18] === 167;
|
|
784
|
+
}
|
|
785
|
+
var lookup = [
|
|
786
|
+
176,
|
|
787
|
+
81,
|
|
788
|
+
104,
|
|
789
|
+
224,
|
|
790
|
+
86,
|
|
791
|
+
137,
|
|
792
|
+
237,
|
|
793
|
+
119,
|
|
794
|
+
38,
|
|
795
|
+
26,
|
|
796
|
+
193,
|
|
797
|
+
161,
|
|
798
|
+
210,
|
|
799
|
+
126,
|
|
800
|
+
150,
|
|
801
|
+
81,
|
|
802
|
+
93,
|
|
803
|
+
13,
|
|
804
|
+
236,
|
|
805
|
+
249,
|
|
806
|
+
89,
|
|
807
|
+
235,
|
|
808
|
+
88,
|
|
809
|
+
24,
|
|
810
|
+
113,
|
|
811
|
+
81,
|
|
812
|
+
214,
|
|
813
|
+
131,
|
|
814
|
+
130,
|
|
815
|
+
199,
|
|
816
|
+
2,
|
|
817
|
+
169,
|
|
818
|
+
39,
|
|
819
|
+
165,
|
|
820
|
+
171,
|
|
821
|
+
41
|
|
822
|
+
];
|
|
823
|
+
function decryptState(data) {
|
|
824
|
+
const offset1 = getNibble(data, 38);
|
|
825
|
+
const offset2 = getNibble(data, 39);
|
|
826
|
+
const output = new Uint8Array(MESSAGE_LENGTH);
|
|
827
|
+
for (let i = 0; i < MESSAGE_LENGTH; i++) {
|
|
828
|
+
output[i] = data[i] + lookup[offset1 + i] + lookup[offset2 + i];
|
|
829
|
+
}
|
|
830
|
+
return output;
|
|
831
|
+
}
|
|
832
|
+
async function decodeState(data) {
|
|
833
|
+
if (!probablyEncrypted(data)) {
|
|
834
|
+
return data;
|
|
835
|
+
}
|
|
836
|
+
return decryptState(data);
|
|
837
|
+
}
|
|
838
|
+
var GiiKERCube = class _GiiKERCube extends BluetoothPuzzle {
|
|
839
|
+
constructor(server, cubeCharacteristic, originalValue) {
|
|
840
|
+
super();
|
|
841
|
+
this.server = server;
|
|
842
|
+
this.cubeCharacteristic = cubeCharacteristic;
|
|
843
|
+
this.originalValue = originalValue;
|
|
844
|
+
}
|
|
845
|
+
server;
|
|
846
|
+
cubeCharacteristic;
|
|
847
|
+
originalValue;
|
|
848
|
+
static async connect(server) {
|
|
849
|
+
const cubeService = await server.getPrimaryService(UUIDs2.cubeService);
|
|
850
|
+
debugLog("Service:", cubeService);
|
|
851
|
+
const cubeCharacteristic = await cubeService.getCharacteristic(
|
|
852
|
+
UUIDs2.cubeCharacteristic
|
|
853
|
+
);
|
|
854
|
+
debugLog("Characteristic:", cubeCharacteristic);
|
|
855
|
+
const originalValue = await decodeState(
|
|
856
|
+
new Uint8Array((await cubeCharacteristic.readValue()).buffer)
|
|
857
|
+
);
|
|
858
|
+
debugLog("Original value:", originalValue);
|
|
859
|
+
const cube = new _GiiKERCube(server, cubeCharacteristic, originalValue);
|
|
860
|
+
await cubeCharacteristic.startNotifications();
|
|
861
|
+
cubeCharacteristic.addEventListener(
|
|
862
|
+
"characteristicvaluechanged",
|
|
863
|
+
cube.onCubeCharacteristicChanged.bind(cube)
|
|
864
|
+
);
|
|
865
|
+
return cube;
|
|
866
|
+
}
|
|
867
|
+
name() {
|
|
868
|
+
return this.server.device.name;
|
|
869
|
+
}
|
|
870
|
+
disconnect() {
|
|
871
|
+
this.server.disconnect();
|
|
872
|
+
}
|
|
873
|
+
async getPattern() {
|
|
874
|
+
return this.toReid333(
|
|
875
|
+
new Uint8Array((await this.cubeCharacteristic.readValue()).buffer)
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
getBit(val, i) {
|
|
879
|
+
const n = i / 8 | 0;
|
|
880
|
+
const shift = 7 - i % 8;
|
|
881
|
+
return val[n] >> shift & 1;
|
|
882
|
+
}
|
|
883
|
+
toReid333(val) {
|
|
884
|
+
const patternData = {
|
|
885
|
+
EDGES: {
|
|
886
|
+
pieces: new Array(12),
|
|
887
|
+
orientation: new Array(12)
|
|
888
|
+
},
|
|
889
|
+
CORNERS: {
|
|
890
|
+
pieces: new Array(8),
|
|
891
|
+
orientation: new Array(8)
|
|
892
|
+
},
|
|
893
|
+
CENTERS: Reid333SolvedCenters
|
|
894
|
+
};
|
|
895
|
+
for (let i = 0; i < 12; i++) {
|
|
896
|
+
const gi = epReid333toGiiKER[i];
|
|
897
|
+
patternData["EDGES"].pieces[i] = epGiiKERtoReid333[getNibble(val, gi + 16) - 1];
|
|
898
|
+
patternData["EDGES"].orientation[i] = this.getBit(val, gi + 112) ^ preEO[patternData["EDGES"].pieces[i]] ^ postEO[i];
|
|
899
|
+
}
|
|
900
|
+
for (let i = 0; i < 8; i++) {
|
|
901
|
+
const gi = cpReid333toGiiKER[i];
|
|
902
|
+
patternData["CORNERS"].pieces[i] = cpGiiKERtoReid333[getNibble(val, gi) - 1];
|
|
903
|
+
patternData["CORNERS"].orientation[i] = (getNibble(val, gi + 8) * coFlip[gi] + preCO[patternData["CORNERS"].pieces[i]] + postCO[i]) % 3;
|
|
904
|
+
}
|
|
905
|
+
return new KPattern(experimental3x3x3KPuzzle, patternData);
|
|
906
|
+
}
|
|
907
|
+
async onCubeCharacteristicChanged(event) {
|
|
908
|
+
const val = await decodeState(new Uint8Array(event.target.value.buffer));
|
|
909
|
+
debugLog(val);
|
|
910
|
+
debugLog(val);
|
|
911
|
+
if (this.isRepeatedInitialValue(val)) {
|
|
912
|
+
debugLog("Skipping repeated initial value.");
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
const giikerState = [];
|
|
916
|
+
for (let i = 0; i < MESSAGE_LENGTH; i++) {
|
|
917
|
+
giikerState.push(Math.floor(val[i] / 16));
|
|
918
|
+
giikerState.push(val[i] % 16);
|
|
919
|
+
}
|
|
920
|
+
debugLog(giikerState);
|
|
921
|
+
const str = giikerStateStr(giikerState);
|
|
922
|
+
debugLog(str);
|
|
923
|
+
this.dispatchAlgLeaf({
|
|
924
|
+
latestAlgLeaf: giikerMoveToAlgMove(giikerState[32], giikerState[33]),
|
|
925
|
+
timeStamp: event.timeStamp,
|
|
926
|
+
debug: {
|
|
927
|
+
stateStr: str
|
|
928
|
+
},
|
|
929
|
+
pattern: this.toReid333(val)
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
isRepeatedInitialValue(val) {
|
|
933
|
+
if (typeof this.originalValue === "undefined") {
|
|
934
|
+
throw new Error("GiiKERCube has uninitialized original value.");
|
|
935
|
+
}
|
|
936
|
+
if (this.originalValue === null) {
|
|
937
|
+
return false;
|
|
938
|
+
}
|
|
939
|
+
const originalValue = this.originalValue;
|
|
940
|
+
this.originalValue = null;
|
|
941
|
+
debugLog("Comparing against original value.");
|
|
942
|
+
for (let i = 0; i < MESSAGE_LENGTH - 2; i++) {
|
|
943
|
+
if (originalValue[i] !== val[i]) {
|
|
944
|
+
debugLog("Different at index ", i);
|
|
945
|
+
return false;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
return true;
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
var giiKERConfig = {
|
|
952
|
+
connect: GiiKERCube.connect.bind(GiiKERCube),
|
|
953
|
+
prefixes: ["Gi", "", "Mi", "Hi-"],
|
|
954
|
+
// Hack
|
|
955
|
+
filters: [
|
|
956
|
+
// Known prefixes: GiC, GiS (3x3x3), Gi2 (2x2x2)
|
|
957
|
+
// Suspected prefixes GiY, Gi3
|
|
958
|
+
{ namePrefix: "Gi" },
|
|
959
|
+
{ namePrefix: "Mi" },
|
|
960
|
+
{ namePrefix: "Hi-" },
|
|
961
|
+
{ services: ["0000aadb-0000-1000-8000-00805f9b34fb"] },
|
|
962
|
+
{ services: ["0000aaaa-0000-1000-8000-00805f9b34fb"] },
|
|
963
|
+
{ services: ["0000fe95-0000-1000-8000-00805f9b34fb"] }
|
|
964
|
+
],
|
|
965
|
+
optionalServices: [
|
|
966
|
+
// "00001530-1212-efde-1523-785feabcd123",
|
|
967
|
+
// "0000aaaa-0000-1000-8000-00805f9b34fb",
|
|
968
|
+
UUIDs2.cubeService
|
|
969
|
+
// "0000180f-0000-1000-8000-00805f9b34fb",
|
|
970
|
+
// "0000180a-0000-1000-8000-00805f9b34fb"
|
|
971
|
+
]
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
// src/cubing/bluetooth/smart-puzzle/gocube.ts
|
|
975
|
+
import { Quaternion as Quaternion3 } from "three/src/math/Quaternion.js";
|
|
976
|
+
var UUIDs3 = {
|
|
977
|
+
goCubeService: "6e400001-b5a3-f393-e0a9-e50e24dcca9e",
|
|
978
|
+
goCubeStateCharacteristic: "6e400003-b5a3-f393-e0a9-e50e24dcca9e"
|
|
979
|
+
};
|
|
980
|
+
function buf2hex2(buffer) {
|
|
981
|
+
return Array.prototype.map.call(
|
|
982
|
+
new Uint8Array(buffer),
|
|
983
|
+
(x) => `00${x.toString(16)}`.slice(-2)
|
|
984
|
+
).join(" ");
|
|
985
|
+
}
|
|
986
|
+
function bufferToString(buffer) {
|
|
987
|
+
const byteView = new Uint8Array(buffer);
|
|
988
|
+
let str = "";
|
|
989
|
+
for (const charCode of byteView) {
|
|
990
|
+
str += String.fromCharCode(charCode);
|
|
991
|
+
}
|
|
992
|
+
return str;
|
|
993
|
+
}
|
|
994
|
+
var moveMap = [
|
|
995
|
+
new Move("B", 1),
|
|
996
|
+
new Move("B", -1),
|
|
997
|
+
new Move("F", 1),
|
|
998
|
+
new Move("F", -1),
|
|
999
|
+
new Move("U", 1),
|
|
1000
|
+
new Move("U", -1),
|
|
1001
|
+
new Move("D", 1),
|
|
1002
|
+
new Move("D", -1),
|
|
1003
|
+
new Move("R", 1),
|
|
1004
|
+
new Move("R", -1),
|
|
1005
|
+
new Move("L", 1),
|
|
1006
|
+
new Move("L", -1)
|
|
1007
|
+
];
|
|
1008
|
+
var GoCube = class _GoCube extends BluetoothPuzzle {
|
|
1009
|
+
constructor(server, goCubeStateCharacteristic) {
|
|
1010
|
+
super();
|
|
1011
|
+
this.server = server;
|
|
1012
|
+
this.goCubeStateCharacteristic = goCubeStateCharacteristic;
|
|
1013
|
+
}
|
|
1014
|
+
server;
|
|
1015
|
+
goCubeStateCharacteristic;
|
|
1016
|
+
// We have to perform async operations before we call the constructor.
|
|
1017
|
+
static async connect(server) {
|
|
1018
|
+
const service = await server.getPrimaryService(UUIDs3.goCubeService);
|
|
1019
|
+
debugLog({ service });
|
|
1020
|
+
const goCubeStateCharacteristic = await service.getCharacteristic(
|
|
1021
|
+
UUIDs3.goCubeStateCharacteristic
|
|
1022
|
+
);
|
|
1023
|
+
debugLog({ goCubeStateCharacteristic });
|
|
1024
|
+
const cube = new _GoCube(server, goCubeStateCharacteristic);
|
|
1025
|
+
await goCubeStateCharacteristic.startNotifications();
|
|
1026
|
+
goCubeStateCharacteristic.addEventListener(
|
|
1027
|
+
"characteristicvaluechanged",
|
|
1028
|
+
cube.onCubeCharacteristicChanged.bind(cube)
|
|
1029
|
+
);
|
|
1030
|
+
return cube;
|
|
1031
|
+
}
|
|
1032
|
+
// public async getState(): Promise<PuzzleState> {
|
|
1033
|
+
// return new Promise((resolve, reject) => {
|
|
1034
|
+
// this.resolve = (value: any) => {
|
|
1035
|
+
// resolve(buf2hex(value.buffer) as any);
|
|
1036
|
+
// };
|
|
1037
|
+
// this.goCubeStateCharacteristic.startNotifications();
|
|
1038
|
+
// });
|
|
1039
|
+
// }
|
|
1040
|
+
recorded = [];
|
|
1041
|
+
homeQuatInverse = null;
|
|
1042
|
+
lastRawQuat = new Quaternion3(0, 0, 0, 1);
|
|
1043
|
+
currentQuat = new Quaternion3(0, 0, 0, 1);
|
|
1044
|
+
lastTarget = new Quaternion3(0, 0, 0, 1);
|
|
1045
|
+
alg = new Alg();
|
|
1046
|
+
disconnect() {
|
|
1047
|
+
this.server.disconnect();
|
|
1048
|
+
}
|
|
1049
|
+
reset() {
|
|
1050
|
+
this.resetAlg();
|
|
1051
|
+
this.resetOrientation();
|
|
1052
|
+
}
|
|
1053
|
+
resetAlg(alg) {
|
|
1054
|
+
this.alg = alg || new Alg();
|
|
1055
|
+
}
|
|
1056
|
+
resetOrientation() {
|
|
1057
|
+
this.homeQuatInverse = this.lastRawQuat.clone().invert();
|
|
1058
|
+
this.currentQuat = new Quaternion3(0, 0, 0, 1);
|
|
1059
|
+
this.lastTarget = new Quaternion3(0, 0, 0, 1);
|
|
1060
|
+
}
|
|
1061
|
+
name() {
|
|
1062
|
+
return this.server.device.name;
|
|
1063
|
+
}
|
|
1064
|
+
onCubeCharacteristicChanged(event) {
|
|
1065
|
+
const buffer = event.target.value;
|
|
1066
|
+
this.recorded.push([event.timeStamp, buf2hex2(buffer.buffer)]);
|
|
1067
|
+
if (buffer.byteLength < 16) {
|
|
1068
|
+
for (let i = 3; i < buffer.byteLength - 4; i += 2) {
|
|
1069
|
+
const move = moveMap[buffer.getUint8(i)];
|
|
1070
|
+
this.alg = experimentalAppendMove(this.alg, move);
|
|
1071
|
+
this.dispatchAlgLeaf({
|
|
1072
|
+
latestAlgLeaf: moveMap[buffer.getUint8(i)],
|
|
1073
|
+
timeStamp: event.timeStamp,
|
|
1074
|
+
debug: {
|
|
1075
|
+
stateStr: buf2hex2(buffer.buffer)
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
} else {
|
|
1080
|
+
const coords = bufferToString(
|
|
1081
|
+
buffer.buffer.slice(3, buffer.byteLength - 3)
|
|
1082
|
+
).split("#").map((s) => parseInt(s, 10) / 16384);
|
|
1083
|
+
const quat = new Quaternion3(coords[0], coords[1], coords[2], coords[3]);
|
|
1084
|
+
this.lastRawQuat = quat.clone();
|
|
1085
|
+
if (!this.homeQuatInverse) {
|
|
1086
|
+
this.homeQuatInverse = quat.clone().invert();
|
|
1087
|
+
}
|
|
1088
|
+
const targetQuat = quat.clone().multiply(this.homeQuatInverse.clone());
|
|
1089
|
+
targetQuat.y = -targetQuat.y;
|
|
1090
|
+
this.lastTarget.slerp(targetQuat, 0.5);
|
|
1091
|
+
this.currentQuat.rotateTowards(this.lastTarget, rotateTowardsRate);
|
|
1092
|
+
this.dispatchOrientation({
|
|
1093
|
+
quaternion: this.currentQuat,
|
|
1094
|
+
timeStamp: event.timeStamp
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
var rotateTowardsRate = 0.5;
|
|
1100
|
+
var goCubeConfig = {
|
|
1101
|
+
connect: GoCube.connect.bind(GoCube),
|
|
1102
|
+
prefixes: ["GoCube", "Rubik"],
|
|
1103
|
+
filters: [{ namePrefix: "GoCube" }, { namePrefix: "Rubik" }],
|
|
1104
|
+
optionalServices: [UUIDs3.goCubeService]
|
|
1105
|
+
};
|
|
1106
|
+
|
|
1107
|
+
// src/cubing/bluetooth/smart-puzzle/endianness.ts
|
|
1108
|
+
function flipBitOrder(v, numBits) {
|
|
1109
|
+
let result = 0;
|
|
1110
|
+
for (let i = 0; i < numBits; i++) {
|
|
1111
|
+
const shiftLeft = numBits - 1 - 2 * i;
|
|
1112
|
+
const unShiftedBit = v & 1 << i;
|
|
1113
|
+
result += shiftLeft < 0 ? unShiftedBit >> -shiftLeft : unShiftedBit << shiftLeft;
|
|
1114
|
+
}
|
|
1115
|
+
return result;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// src/cubing/bluetooth/smart-puzzle/Heykube.ts
|
|
1119
|
+
var UUIDs4 = {
|
|
1120
|
+
heykubeService: "b46a791a-8273-4fc1-9e67-94d3dc2aac1c",
|
|
1121
|
+
stateCharacteristic: "a2f41a4e-0e31-4bbc-9389-4253475481fb",
|
|
1122
|
+
batteryCharacteristic: "fd51b3ba-99c7-49c6-9f85-5644ff56a378"
|
|
1123
|
+
};
|
|
1124
|
+
var HeykubeCube = class _HeykubeCube extends BluetoothPuzzle {
|
|
1125
|
+
constructor(_kpuzzle, _service, device, server, stateCharacteristic) {
|
|
1126
|
+
super();
|
|
1127
|
+
this.server = server;
|
|
1128
|
+
this.stateCharacteristic = stateCharacteristic;
|
|
1129
|
+
device.addEventListener(
|
|
1130
|
+
"gattserverdisconnected",
|
|
1131
|
+
this.onDisconnect.bind(this)
|
|
1132
|
+
);
|
|
1133
|
+
this.stateCharacteristic.startNotifications();
|
|
1134
|
+
this.startTrackingMoves();
|
|
1135
|
+
}
|
|
1136
|
+
server;
|
|
1137
|
+
stateCharacteristic;
|
|
1138
|
+
// We have to perform async operations before we call the constructor.
|
|
1139
|
+
static async connect(server, device) {
|
|
1140
|
+
const service = await server.getPrimaryService(UUIDs4.heykubeService);
|
|
1141
|
+
debugLog("Service:", service);
|
|
1142
|
+
const stateCharacteristic = await service.getCharacteristic(
|
|
1143
|
+
UUIDs4.stateCharacteristic
|
|
1144
|
+
);
|
|
1145
|
+
debugLog("Characteristic:", stateCharacteristic);
|
|
1146
|
+
const cube = new _HeykubeCube(
|
|
1147
|
+
await puzzles["3x3x3"].kpuzzle(),
|
|
1148
|
+
service,
|
|
1149
|
+
device,
|
|
1150
|
+
server,
|
|
1151
|
+
stateCharacteristic
|
|
1152
|
+
);
|
|
1153
|
+
return cube;
|
|
1154
|
+
}
|
|
1155
|
+
name() {
|
|
1156
|
+
return this.server.device.name;
|
|
1157
|
+
}
|
|
1158
|
+
disconnect() {
|
|
1159
|
+
this.server.disconnect();
|
|
1160
|
+
}
|
|
1161
|
+
onDisconnect() {
|
|
1162
|
+
this.dispatchEvent(new CustomEvent("disconnect"));
|
|
1163
|
+
}
|
|
1164
|
+
startTrackingMoves() {
|
|
1165
|
+
this.stateCharacteristic.addEventListener(
|
|
1166
|
+
"characteristicvaluechanged",
|
|
1167
|
+
(e) => this.onStateCharacteristic(e)
|
|
1168
|
+
);
|
|
1169
|
+
}
|
|
1170
|
+
// public stopTrackingMoves(): void {}
|
|
1171
|
+
// public async getBattery(): Promise<number> {
|
|
1172
|
+
// return new Uint8Array(
|
|
1173
|
+
// await this.readActualAngleAndBatteryCharacteristic(),
|
|
1174
|
+
// )[7];
|
|
1175
|
+
// }srcElement: BluetoothRemoteGATTCharacteristic
|
|
1176
|
+
onStateCharacteristic(event) {
|
|
1177
|
+
const state = this.decodeState(event.target.value);
|
|
1178
|
+
this.dispatchAlgLeaf({
|
|
1179
|
+
latestAlgLeaf: state.latestMove,
|
|
1180
|
+
timeStamp: event.timeStamp,
|
|
1181
|
+
pattern: state.pattern
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
decodeState(dv) {
|
|
1185
|
+
const moves = [
|
|
1186
|
+
new Move("U"),
|
|
1187
|
+
new Move("U'"),
|
|
1188
|
+
new Move("B"),
|
|
1189
|
+
new Move("B'"),
|
|
1190
|
+
new Move("F"),
|
|
1191
|
+
new Move("F'"),
|
|
1192
|
+
null,
|
|
1193
|
+
null,
|
|
1194
|
+
new Move("L"),
|
|
1195
|
+
new Move("L'"),
|
|
1196
|
+
new Move("D"),
|
|
1197
|
+
new Move("D'"),
|
|
1198
|
+
new Move("R"),
|
|
1199
|
+
new Move("R'")
|
|
1200
|
+
// null,
|
|
1201
|
+
// null,
|
|
1202
|
+
];
|
|
1203
|
+
const b2 = new Uint8Array(dv.byteLength);
|
|
1204
|
+
for (let i = 0; i < dv.byteLength; i++) {
|
|
1205
|
+
b2[i] = flipBitOrder(dv.getUint8(i), 8);
|
|
1206
|
+
}
|
|
1207
|
+
const components1 = twizzleBinaryToBinaryComponents(
|
|
1208
|
+
b2.slice(0, 11)
|
|
1209
|
+
);
|
|
1210
|
+
const components2 = {
|
|
1211
|
+
epLex: flipBitOrder(components1.epLex, 29),
|
|
1212
|
+
eoMask: flipBitOrder(components1.eoMask, 12),
|
|
1213
|
+
cpLex: flipBitOrder(components1.cpLex, 16),
|
|
1214
|
+
coMask: flipBitOrder(components1.coMask, 13),
|
|
1215
|
+
poIdxL: 0,
|
|
1216
|
+
poIdxU: 7,
|
|
1217
|
+
moSupport: 1,
|
|
1218
|
+
// TODO
|
|
1219
|
+
moMask: 0
|
|
1220
|
+
};
|
|
1221
|
+
return {
|
|
1222
|
+
pattern: binaryComponentsToReid3x3x3(components2),
|
|
1223
|
+
latestMove: moves[b2[20] & 15]
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
async getPattern() {
|
|
1227
|
+
const b1 = await this.stateCharacteristic.readValue();
|
|
1228
|
+
return this.decodeState(b1).pattern;
|
|
1229
|
+
}
|
|
1230
|
+
};
|
|
1231
|
+
var heykubeConfig = {
|
|
1232
|
+
connect: HeykubeCube.connect.bind(HeykubeCube),
|
|
1233
|
+
prefixes: ["HEYKUBE"],
|
|
1234
|
+
filters: [{ namePrefix: "HEYKUBE" }],
|
|
1235
|
+
optionalServices: [UUIDs4.heykubeService]
|
|
1236
|
+
};
|
|
1237
|
+
|
|
1238
|
+
// src/cubing/bluetooth/smart-puzzle/qiyi.ts
|
|
1239
|
+
var UUIDs5 = {
|
|
1240
|
+
qiyiMainService: 65520,
|
|
1241
|
+
qiyiMainCharacteristic: 65526
|
|
1242
|
+
};
|
|
1243
|
+
var qiyiMoveToBlockMove = {
|
|
1244
|
+
1: new Move("L", -1),
|
|
1245
|
+
2: new Move("L"),
|
|
1246
|
+
3: new Move("R", -1),
|
|
1247
|
+
4: new Move("R"),
|
|
1248
|
+
5: new Move("D", -1),
|
|
1249
|
+
6: new Move("D"),
|
|
1250
|
+
7: new Move("U", -1),
|
|
1251
|
+
8: new Move("U"),
|
|
1252
|
+
9: new Move("F", -1),
|
|
1253
|
+
10: new Move("F"),
|
|
1254
|
+
11: new Move("B", -1),
|
|
1255
|
+
12: new Move("B")
|
|
1256
|
+
};
|
|
1257
|
+
var faceOrder2 = "LRDUFB";
|
|
1258
|
+
var qiyiCornerMappings = [
|
|
1259
|
+
[8, 20, 9],
|
|
1260
|
+
// UFR,
|
|
1261
|
+
[2, 11, 45],
|
|
1262
|
+
// URB,
|
|
1263
|
+
[0, 47, 36],
|
|
1264
|
+
// UBL,
|
|
1265
|
+
[6, 38, 18],
|
|
1266
|
+
// ULF,
|
|
1267
|
+
[29, 15, 26],
|
|
1268
|
+
// DRF,
|
|
1269
|
+
[27, 24, 44],
|
|
1270
|
+
// DFL,
|
|
1271
|
+
[33, 42, 53],
|
|
1272
|
+
// DLB,
|
|
1273
|
+
[35, 51, 17]
|
|
1274
|
+
// DBR,
|
|
1275
|
+
];
|
|
1276
|
+
var qiyiEdgeMappings = [
|
|
1277
|
+
[7, 19],
|
|
1278
|
+
// UF,
|
|
1279
|
+
[5, 10],
|
|
1280
|
+
// UR,
|
|
1281
|
+
[1, 46],
|
|
1282
|
+
// UB,
|
|
1283
|
+
[3, 37],
|
|
1284
|
+
// UL,
|
|
1285
|
+
[28, 25],
|
|
1286
|
+
// DF,
|
|
1287
|
+
[32, 16],
|
|
1288
|
+
// DR,
|
|
1289
|
+
[34, 52],
|
|
1290
|
+
// DB,
|
|
1291
|
+
[30, 43],
|
|
1292
|
+
// DL,
|
|
1293
|
+
[23, 12],
|
|
1294
|
+
// FR,
|
|
1295
|
+
[21, 41],
|
|
1296
|
+
// FL,
|
|
1297
|
+
[48, 14],
|
|
1298
|
+
// BR,
|
|
1299
|
+
[50, 39]
|
|
1300
|
+
// BL,
|
|
1301
|
+
];
|
|
1302
|
+
function generateChecksum(data) {
|
|
1303
|
+
const TABLE = new Uint16Array([
|
|
1304
|
+
0,
|
|
1305
|
+
49345,
|
|
1306
|
+
49537,
|
|
1307
|
+
320,
|
|
1308
|
+
49921,
|
|
1309
|
+
960,
|
|
1310
|
+
640,
|
|
1311
|
+
49729,
|
|
1312
|
+
50689,
|
|
1313
|
+
1728,
|
|
1314
|
+
1920,
|
|
1315
|
+
51009,
|
|
1316
|
+
1280,
|
|
1317
|
+
50625,
|
|
1318
|
+
50305,
|
|
1319
|
+
1088,
|
|
1320
|
+
52225,
|
|
1321
|
+
3264,
|
|
1322
|
+
3456,
|
|
1323
|
+
52545,
|
|
1324
|
+
3840,
|
|
1325
|
+
53185,
|
|
1326
|
+
52865,
|
|
1327
|
+
3648,
|
|
1328
|
+
2560,
|
|
1329
|
+
51905,
|
|
1330
|
+
52097,
|
|
1331
|
+
2880,
|
|
1332
|
+
51457,
|
|
1333
|
+
2496,
|
|
1334
|
+
2176,
|
|
1335
|
+
51265,
|
|
1336
|
+
55297,
|
|
1337
|
+
6336,
|
|
1338
|
+
6528,
|
|
1339
|
+
55617,
|
|
1340
|
+
6912,
|
|
1341
|
+
56257,
|
|
1342
|
+
55937,
|
|
1343
|
+
6720,
|
|
1344
|
+
7680,
|
|
1345
|
+
57025,
|
|
1346
|
+
57217,
|
|
1347
|
+
8e3,
|
|
1348
|
+
56577,
|
|
1349
|
+
7616,
|
|
1350
|
+
7296,
|
|
1351
|
+
56385,
|
|
1352
|
+
5120,
|
|
1353
|
+
54465,
|
|
1354
|
+
54657,
|
|
1355
|
+
5440,
|
|
1356
|
+
55041,
|
|
1357
|
+
6080,
|
|
1358
|
+
5760,
|
|
1359
|
+
54849,
|
|
1360
|
+
53761,
|
|
1361
|
+
4800,
|
|
1362
|
+
4992,
|
|
1363
|
+
54081,
|
|
1364
|
+
4352,
|
|
1365
|
+
53697,
|
|
1366
|
+
53377,
|
|
1367
|
+
4160,
|
|
1368
|
+
61441,
|
|
1369
|
+
12480,
|
|
1370
|
+
12672,
|
|
1371
|
+
61761,
|
|
1372
|
+
13056,
|
|
1373
|
+
62401,
|
|
1374
|
+
62081,
|
|
1375
|
+
12864,
|
|
1376
|
+
13824,
|
|
1377
|
+
63169,
|
|
1378
|
+
63361,
|
|
1379
|
+
14144,
|
|
1380
|
+
62721,
|
|
1381
|
+
13760,
|
|
1382
|
+
13440,
|
|
1383
|
+
62529,
|
|
1384
|
+
15360,
|
|
1385
|
+
64705,
|
|
1386
|
+
64897,
|
|
1387
|
+
15680,
|
|
1388
|
+
65281,
|
|
1389
|
+
16320,
|
|
1390
|
+
16e3,
|
|
1391
|
+
65089,
|
|
1392
|
+
64001,
|
|
1393
|
+
15040,
|
|
1394
|
+
15232,
|
|
1395
|
+
64321,
|
|
1396
|
+
14592,
|
|
1397
|
+
63937,
|
|
1398
|
+
63617,
|
|
1399
|
+
14400,
|
|
1400
|
+
10240,
|
|
1401
|
+
59585,
|
|
1402
|
+
59777,
|
|
1403
|
+
10560,
|
|
1404
|
+
60161,
|
|
1405
|
+
11200,
|
|
1406
|
+
10880,
|
|
1407
|
+
59969,
|
|
1408
|
+
60929,
|
|
1409
|
+
11968,
|
|
1410
|
+
12160,
|
|
1411
|
+
61249,
|
|
1412
|
+
11520,
|
|
1413
|
+
60865,
|
|
1414
|
+
60545,
|
|
1415
|
+
11328,
|
|
1416
|
+
58369,
|
|
1417
|
+
9408,
|
|
1418
|
+
9600,
|
|
1419
|
+
58689,
|
|
1420
|
+
9984,
|
|
1421
|
+
59329,
|
|
1422
|
+
59009,
|
|
1423
|
+
9792,
|
|
1424
|
+
8704,
|
|
1425
|
+
58049,
|
|
1426
|
+
58241,
|
|
1427
|
+
9024,
|
|
1428
|
+
57601,
|
|
1429
|
+
8640,
|
|
1430
|
+
8320,
|
|
1431
|
+
57409,
|
|
1432
|
+
40961,
|
|
1433
|
+
24768,
|
|
1434
|
+
24960,
|
|
1435
|
+
41281,
|
|
1436
|
+
25344,
|
|
1437
|
+
41921,
|
|
1438
|
+
41601,
|
|
1439
|
+
25152,
|
|
1440
|
+
26112,
|
|
1441
|
+
42689,
|
|
1442
|
+
42881,
|
|
1443
|
+
26432,
|
|
1444
|
+
42241,
|
|
1445
|
+
26048,
|
|
1446
|
+
25728,
|
|
1447
|
+
42049,
|
|
1448
|
+
27648,
|
|
1449
|
+
44225,
|
|
1450
|
+
44417,
|
|
1451
|
+
27968,
|
|
1452
|
+
44801,
|
|
1453
|
+
28608,
|
|
1454
|
+
28288,
|
|
1455
|
+
44609,
|
|
1456
|
+
43521,
|
|
1457
|
+
27328,
|
|
1458
|
+
27520,
|
|
1459
|
+
43841,
|
|
1460
|
+
26880,
|
|
1461
|
+
43457,
|
|
1462
|
+
43137,
|
|
1463
|
+
26688,
|
|
1464
|
+
30720,
|
|
1465
|
+
47297,
|
|
1466
|
+
47489,
|
|
1467
|
+
31040,
|
|
1468
|
+
47873,
|
|
1469
|
+
31680,
|
|
1470
|
+
31360,
|
|
1471
|
+
47681,
|
|
1472
|
+
48641,
|
|
1473
|
+
32448,
|
|
1474
|
+
32640,
|
|
1475
|
+
48961,
|
|
1476
|
+
32e3,
|
|
1477
|
+
48577,
|
|
1478
|
+
48257,
|
|
1479
|
+
31808,
|
|
1480
|
+
46081,
|
|
1481
|
+
29888,
|
|
1482
|
+
30080,
|
|
1483
|
+
46401,
|
|
1484
|
+
30464,
|
|
1485
|
+
47041,
|
|
1486
|
+
46721,
|
|
1487
|
+
30272,
|
|
1488
|
+
29184,
|
|
1489
|
+
45761,
|
|
1490
|
+
45953,
|
|
1491
|
+
29504,
|
|
1492
|
+
45313,
|
|
1493
|
+
29120,
|
|
1494
|
+
28800,
|
|
1495
|
+
45121,
|
|
1496
|
+
20480,
|
|
1497
|
+
37057,
|
|
1498
|
+
37249,
|
|
1499
|
+
20800,
|
|
1500
|
+
37633,
|
|
1501
|
+
21440,
|
|
1502
|
+
21120,
|
|
1503
|
+
37441,
|
|
1504
|
+
38401,
|
|
1505
|
+
22208,
|
|
1506
|
+
22400,
|
|
1507
|
+
38721,
|
|
1508
|
+
21760,
|
|
1509
|
+
38337,
|
|
1510
|
+
38017,
|
|
1511
|
+
21568,
|
|
1512
|
+
39937,
|
|
1513
|
+
23744,
|
|
1514
|
+
23936,
|
|
1515
|
+
40257,
|
|
1516
|
+
24320,
|
|
1517
|
+
40897,
|
|
1518
|
+
40577,
|
|
1519
|
+
24128,
|
|
1520
|
+
23040,
|
|
1521
|
+
39617,
|
|
1522
|
+
39809,
|
|
1523
|
+
23360,
|
|
1524
|
+
39169,
|
|
1525
|
+
22976,
|
|
1526
|
+
22656,
|
|
1527
|
+
38977,
|
|
1528
|
+
34817,
|
|
1529
|
+
18624,
|
|
1530
|
+
18816,
|
|
1531
|
+
35137,
|
|
1532
|
+
19200,
|
|
1533
|
+
35777,
|
|
1534
|
+
35457,
|
|
1535
|
+
19008,
|
|
1536
|
+
19968,
|
|
1537
|
+
36545,
|
|
1538
|
+
36737,
|
|
1539
|
+
20288,
|
|
1540
|
+
36097,
|
|
1541
|
+
19904,
|
|
1542
|
+
19584,
|
|
1543
|
+
35905,
|
|
1544
|
+
17408,
|
|
1545
|
+
33985,
|
|
1546
|
+
34177,
|
|
1547
|
+
17728,
|
|
1548
|
+
34561,
|
|
1549
|
+
18368,
|
|
1550
|
+
18048,
|
|
1551
|
+
34369,
|
|
1552
|
+
33281,
|
|
1553
|
+
17088,
|
|
1554
|
+
17280,
|
|
1555
|
+
33601,
|
|
1556
|
+
16640,
|
|
1557
|
+
33217,
|
|
1558
|
+
32897,
|
|
1559
|
+
16448
|
|
1560
|
+
]);
|
|
1561
|
+
let crc = 65535;
|
|
1562
|
+
for (const dataPoint of data) {
|
|
1563
|
+
const xor = (dataPoint ^ crc) & 255;
|
|
1564
|
+
crc >>= 8;
|
|
1565
|
+
crc ^= TABLE[xor];
|
|
1566
|
+
}
|
|
1567
|
+
return crc;
|
|
1568
|
+
}
|
|
1569
|
+
async function prepareMessage(message, aesKey) {
|
|
1570
|
+
const messageCopyForChecksum = structuredClone(message);
|
|
1571
|
+
const checksum = generateChecksum(messageCopyForChecksum);
|
|
1572
|
+
messageCopyForChecksum.push(checksum & 255);
|
|
1573
|
+
messageCopyForChecksum.push(checksum >> 8);
|
|
1574
|
+
const paddedLength = Math.ceil(messageCopyForChecksum.length / 16) * 16;
|
|
1575
|
+
const paddedArray = new Uint8Array([
|
|
1576
|
+
...messageCopyForChecksum,
|
|
1577
|
+
...Array(paddedLength - messageCopyForChecksum.length).fill(0)
|
|
1578
|
+
]);
|
|
1579
|
+
const encryptedMessage = new Uint8Array(paddedLength);
|
|
1580
|
+
for (let i = 0; i < paddedArray.length / 16; i++) {
|
|
1581
|
+
const encryptedBlock = new Uint8Array(
|
|
1582
|
+
await unsafeEncryptBlock(aesKey, paddedArray.slice(i * 16, (i + 1) * 16))
|
|
1583
|
+
);
|
|
1584
|
+
encryptedMessage.set(encryptedBlock, i * 16);
|
|
1585
|
+
}
|
|
1586
|
+
return encryptedMessage;
|
|
1587
|
+
}
|
|
1588
|
+
async function decryptMessage(encryptedMessage, aesKey) {
|
|
1589
|
+
const decryptedMessage = new Uint8Array(encryptedMessage.length);
|
|
1590
|
+
for (let i = 0; i < encryptedMessage.length / 16; i++) {
|
|
1591
|
+
const decryptedBlock = new Uint8Array(
|
|
1592
|
+
await unsafeDecryptBlock(
|
|
1593
|
+
aesKey,
|
|
1594
|
+
encryptedMessage.slice(i * 16, (i + 1) * 16)
|
|
1595
|
+
)
|
|
1596
|
+
);
|
|
1597
|
+
decryptedMessage.set(decryptedBlock, i * 16);
|
|
1598
|
+
}
|
|
1599
|
+
return decryptedMessage;
|
|
1600
|
+
}
|
|
1601
|
+
function getMacAddress(device) {
|
|
1602
|
+
if (device.name === void 0) {
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
return [
|
|
1606
|
+
204,
|
|
1607
|
+
163,
|
|
1608
|
+
0,
|
|
1609
|
+
0,
|
|
1610
|
+
parseInt(device.name.slice(10, 12), 16),
|
|
1611
|
+
parseInt(device.name.slice(12, 14), 16)
|
|
1612
|
+
];
|
|
1613
|
+
}
|
|
1614
|
+
var MAX_TIMESTAMP_COUNT = 12;
|
|
1615
|
+
var TIMESTAMP_SCALE = 1.6;
|
|
1616
|
+
var CUBE_HELLO = 2;
|
|
1617
|
+
var STATE_CHANGE = 3;
|
|
1618
|
+
var QiyiCube = class _QiyiCube extends BluetoothPuzzle {
|
|
1619
|
+
constructor(kpuzzle, aesKey, server) {
|
|
1620
|
+
super();
|
|
1621
|
+
this.kpuzzle = kpuzzle;
|
|
1622
|
+
this.aesKey = aesKey;
|
|
1623
|
+
this.server = server;
|
|
1624
|
+
this.allTimeStamps = /* @__PURE__ */ new Set();
|
|
1625
|
+
this.allTimeStampsQueue = [];
|
|
1626
|
+
void (async () => {
|
|
1627
|
+
await this.startNotifications();
|
|
1628
|
+
void this.sendAppHello();
|
|
1629
|
+
})();
|
|
1630
|
+
}
|
|
1631
|
+
kpuzzle;
|
|
1632
|
+
aesKey;
|
|
1633
|
+
server;
|
|
1634
|
+
latestTimestamp;
|
|
1635
|
+
allTimeStamps;
|
|
1636
|
+
// Without this set, moves are constantly duplicated
|
|
1637
|
+
allTimeStampsQueue;
|
|
1638
|
+
stickers = [
|
|
1639
|
+
3,
|
|
1640
|
+
3,
|
|
1641
|
+
3,
|
|
1642
|
+
3,
|
|
1643
|
+
3,
|
|
1644
|
+
3,
|
|
1645
|
+
3,
|
|
1646
|
+
3,
|
|
1647
|
+
3,
|
|
1648
|
+
1,
|
|
1649
|
+
1,
|
|
1650
|
+
1,
|
|
1651
|
+
1,
|
|
1652
|
+
1,
|
|
1653
|
+
1,
|
|
1654
|
+
1,
|
|
1655
|
+
1,
|
|
1656
|
+
1,
|
|
1657
|
+
4,
|
|
1658
|
+
4,
|
|
1659
|
+
4,
|
|
1660
|
+
4,
|
|
1661
|
+
4,
|
|
1662
|
+
4,
|
|
1663
|
+
4,
|
|
1664
|
+
4,
|
|
1665
|
+
4,
|
|
1666
|
+
2,
|
|
1667
|
+
2,
|
|
1668
|
+
2,
|
|
1669
|
+
2,
|
|
1670
|
+
2,
|
|
1671
|
+
2,
|
|
1672
|
+
2,
|
|
1673
|
+
2,
|
|
1674
|
+
2,
|
|
1675
|
+
0,
|
|
1676
|
+
0,
|
|
1677
|
+
0,
|
|
1678
|
+
0,
|
|
1679
|
+
0,
|
|
1680
|
+
0,
|
|
1681
|
+
0,
|
|
1682
|
+
0,
|
|
1683
|
+
0,
|
|
1684
|
+
5,
|
|
1685
|
+
5,
|
|
1686
|
+
5,
|
|
1687
|
+
5,
|
|
1688
|
+
5,
|
|
1689
|
+
5,
|
|
1690
|
+
5,
|
|
1691
|
+
5,
|
|
1692
|
+
5
|
|
1693
|
+
];
|
|
1694
|
+
batteryLevel = 100;
|
|
1695
|
+
static async connect(server) {
|
|
1696
|
+
const aesKey = await importKey(
|
|
1697
|
+
new Uint8Array([
|
|
1698
|
+
87,
|
|
1699
|
+
177,
|
|
1700
|
+
249,
|
|
1701
|
+
171,
|
|
1702
|
+
205,
|
|
1703
|
+
90,
|
|
1704
|
+
232,
|
|
1705
|
+
167,
|
|
1706
|
+
156,
|
|
1707
|
+
185,
|
|
1708
|
+
140,
|
|
1709
|
+
231,
|
|
1710
|
+
87,
|
|
1711
|
+
140,
|
|
1712
|
+
81,
|
|
1713
|
+
8
|
|
1714
|
+
])
|
|
1715
|
+
);
|
|
1716
|
+
return new _QiyiCube(await puzzles["3x3x3"].kpuzzle(), aesKey, server);
|
|
1717
|
+
}
|
|
1718
|
+
async sendAppHello() {
|
|
1719
|
+
const mainService = await this.server.getPrimaryService(
|
|
1720
|
+
UUIDs5.qiyiMainService
|
|
1721
|
+
);
|
|
1722
|
+
const mainCharacteristic = await mainService.getCharacteristic(
|
|
1723
|
+
UUIDs5.qiyiMainCharacteristic
|
|
1724
|
+
);
|
|
1725
|
+
for (let macGuessCounter = 0; macGuessCounter < 8; macGuessCounter++) {
|
|
1726
|
+
const mac = getMacAddress(this.server.device);
|
|
1727
|
+
mac[3] = macGuessCounter;
|
|
1728
|
+
const reversedMac = mac.reverse();
|
|
1729
|
+
const appHello = [
|
|
1730
|
+
254,
|
|
1731
|
+
21,
|
|
1732
|
+
0,
|
|
1733
|
+
0,
|
|
1734
|
+
0,
|
|
1735
|
+
0,
|
|
1736
|
+
0,
|
|
1737
|
+
0,
|
|
1738
|
+
0,
|
|
1739
|
+
0,
|
|
1740
|
+
0,
|
|
1741
|
+
0,
|
|
1742
|
+
0,
|
|
1743
|
+
...reversedMac
|
|
1744
|
+
];
|
|
1745
|
+
const appHelloMessage = await prepareMessage(appHello, this.aesKey);
|
|
1746
|
+
await mainCharacteristic.writeValue(appHelloMessage);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
async startNotifications() {
|
|
1750
|
+
const mainService = await this.server.getPrimaryService(
|
|
1751
|
+
UUIDs5.qiyiMainService
|
|
1752
|
+
);
|
|
1753
|
+
const mainCharacteristic = await mainService.getCharacteristic(
|
|
1754
|
+
UUIDs5.qiyiMainCharacteristic
|
|
1755
|
+
);
|
|
1756
|
+
mainCharacteristic.addEventListener(
|
|
1757
|
+
"characteristicvaluechanged",
|
|
1758
|
+
this.cubeMessageHandler.bind(this)
|
|
1759
|
+
);
|
|
1760
|
+
await mainCharacteristic.startNotifications();
|
|
1761
|
+
}
|
|
1762
|
+
async cubeMessageHandler(event) {
|
|
1763
|
+
const characteristic = event.target;
|
|
1764
|
+
const decryptedMessage = await decryptMessage(
|
|
1765
|
+
new Uint8Array(characteristic.value.buffer),
|
|
1766
|
+
this.aesKey
|
|
1767
|
+
);
|
|
1768
|
+
const opCode = decryptedMessage[2];
|
|
1769
|
+
let needsAck = false;
|
|
1770
|
+
switch (opCode) {
|
|
1771
|
+
case CUBE_HELLO: {
|
|
1772
|
+
const initialState = decryptedMessage.slice(7, 34);
|
|
1773
|
+
this.updateState(initialState);
|
|
1774
|
+
this.batteryLevel = decryptedMessage[35];
|
|
1775
|
+
needsAck = true;
|
|
1776
|
+
break;
|
|
1777
|
+
}
|
|
1778
|
+
case STATE_CHANGE: {
|
|
1779
|
+
const state = decryptedMessage.slice(7, 34);
|
|
1780
|
+
this.updateState(state);
|
|
1781
|
+
const latestMove = qiyiMoveToBlockMove[decryptedMessage[34]];
|
|
1782
|
+
const latestTimestamp = new DataView(
|
|
1783
|
+
decryptedMessage.slice(3, 7).buffer
|
|
1784
|
+
).getInt32(0);
|
|
1785
|
+
const moves = [[latestMove, latestTimestamp]];
|
|
1786
|
+
const previousMoves = new DataView(
|
|
1787
|
+
decryptedMessage.slice(36, 91).buffer
|
|
1788
|
+
);
|
|
1789
|
+
for (let i = previousMoves.byteLength - 1; i > 0 && previousMoves.getUint8(i) !== 255; i -= 5) {
|
|
1790
|
+
const move = qiyiMoveToBlockMove[previousMoves.getUint8(i)];
|
|
1791
|
+
const timestamp = previousMoves.getUint32(i - 4);
|
|
1792
|
+
if (this.latestTimestamp === void 0 || timestamp <= this.latestTimestamp) {
|
|
1793
|
+
continue;
|
|
1794
|
+
}
|
|
1795
|
+
moves.push([move, timestamp]);
|
|
1796
|
+
}
|
|
1797
|
+
moves.sort((a, b) => a[1] - b[1]);
|
|
1798
|
+
for (const move of moves) {
|
|
1799
|
+
const latestAlgLeaf = move[0];
|
|
1800
|
+
const timeStamp = Math.round(move[1] / TIMESTAMP_SCALE);
|
|
1801
|
+
if (!this.allTimeStamps.has(timeStamp)) {
|
|
1802
|
+
this.dispatchAlgLeaf({
|
|
1803
|
+
latestAlgLeaf,
|
|
1804
|
+
timeStamp
|
|
1805
|
+
});
|
|
1806
|
+
this.allTimeStamps.add(timeStamp);
|
|
1807
|
+
this.allTimeStampsQueue.push(timeStamp);
|
|
1808
|
+
if (this.allTimeStampsQueue.length > MAX_TIMESTAMP_COUNT) {
|
|
1809
|
+
this.allTimeStamps.delete(this.allTimeStampsQueue.shift());
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
this.latestTimestamp = latestTimestamp;
|
|
1814
|
+
needsAck = decryptedMessage[91] === 1;
|
|
1815
|
+
break;
|
|
1816
|
+
}
|
|
1817
|
+
default:
|
|
1818
|
+
console.error(`Opcode not implemented: ${opCode}`);
|
|
1819
|
+
break;
|
|
1820
|
+
}
|
|
1821
|
+
if (needsAck) {
|
|
1822
|
+
await characteristic.writeValue(
|
|
1823
|
+
await prepareMessage(
|
|
1824
|
+
[254, 9, ...decryptedMessage.slice(2, 7)],
|
|
1825
|
+
this.aesKey
|
|
1826
|
+
)
|
|
1827
|
+
);
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
updateState(state) {
|
|
1831
|
+
this.stickers = Array.from(state).flatMap((twoPieces) => [
|
|
1832
|
+
twoPieces & 15,
|
|
1833
|
+
twoPieces >> 4
|
|
1834
|
+
]);
|
|
1835
|
+
}
|
|
1836
|
+
name() {
|
|
1837
|
+
return this.server.device.name;
|
|
1838
|
+
}
|
|
1839
|
+
disconnect() {
|
|
1840
|
+
this.server.disconnect();
|
|
1841
|
+
}
|
|
1842
|
+
async getPattern() {
|
|
1843
|
+
return new KPattern(
|
|
1844
|
+
this.kpuzzle,
|
|
1845
|
+
getPatternData(
|
|
1846
|
+
this.stickers,
|
|
1847
|
+
faceOrder2,
|
|
1848
|
+
qiyiEdgeMappings,
|
|
1849
|
+
qiyiCornerMappings
|
|
1850
|
+
)
|
|
1851
|
+
);
|
|
1852
|
+
}
|
|
1853
|
+
getBattery() {
|
|
1854
|
+
return this.batteryLevel;
|
|
1855
|
+
}
|
|
1856
|
+
};
|
|
1857
|
+
var qiyiConfig = {
|
|
1858
|
+
connect: QiyiCube.connect.bind(QiyiCube),
|
|
1859
|
+
prefixes: ["QY-QYSC"],
|
|
1860
|
+
filters: [
|
|
1861
|
+
{
|
|
1862
|
+
namePrefix: "QY-QYSC"
|
|
1863
|
+
}
|
|
1864
|
+
],
|
|
1865
|
+
optionalServices: [UUIDs5.qiyiMainService]
|
|
1866
|
+
};
|
|
1867
|
+
|
|
1868
|
+
// src/cubing/bluetooth/smart-puzzle/connect.ts
|
|
1869
|
+
var smartPuzzleConfigs = [
|
|
1870
|
+
ganConfig,
|
|
1871
|
+
goCubeConfig,
|
|
1872
|
+
heykubeConfig,
|
|
1873
|
+
qiyiConfig,
|
|
1874
|
+
giiKERConfig
|
|
1875
|
+
// GiiKER must be last, due to Xiaomi naming. TODO: enforce this using tests.
|
|
1876
|
+
];
|
|
1877
|
+
async function connectSmartPuzzle(options) {
|
|
1878
|
+
return bluetoothConnect(smartPuzzleConfigs, options);
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
// src/cubing/bluetooth/smart-robot/GanRobot.ts
|
|
1882
|
+
function buf2hex3(buffer) {
|
|
1883
|
+
return Array.prototype.map.call(
|
|
1884
|
+
new Uint8Array(buffer),
|
|
1885
|
+
(x) => `00${x.toString(16)}`.slice(-2)
|
|
1886
|
+
).join(" ");
|
|
1887
|
+
}
|
|
1888
|
+
var MAX_NIBBLES_PER_WRITE = 18 * 2;
|
|
1889
|
+
var QUANTUM_TURN_DURATION_MS = 150;
|
|
1890
|
+
var DOUBLE_TURN_DURATION_MS = 250;
|
|
1891
|
+
var U_D_SWAP = new Alg("F B R2 L2 B' F'");
|
|
1892
|
+
var U_D_UNSWAP = U_D_SWAP.invert();
|
|
1893
|
+
var F_B_SWAP = new Alg("U D R2 L2 D' U'");
|
|
1894
|
+
var F_B_UNSWAP = F_B_SWAP.invert();
|
|
1895
|
+
var UUIDs6 = {
|
|
1896
|
+
ganRobotService: "0000fff0-0000-1000-8000-00805f9b34fb",
|
|
1897
|
+
statusCharacteristic: "0000fff2-0000-1000-8000-00805f9b34fb",
|
|
1898
|
+
moveCharacteristic: "0000fff3-0000-1000-8000-00805f9b34fb"
|
|
1899
|
+
};
|
|
1900
|
+
var moveMap2 = {
|
|
1901
|
+
R: 0,
|
|
1902
|
+
R2: 1,
|
|
1903
|
+
"R2'": 1,
|
|
1904
|
+
"R'": 2,
|
|
1905
|
+
F: 3,
|
|
1906
|
+
F2: 4,
|
|
1907
|
+
"F2'": 4,
|
|
1908
|
+
"F'": 5,
|
|
1909
|
+
D: 6,
|
|
1910
|
+
D2: 7,
|
|
1911
|
+
"D2'": 7,
|
|
1912
|
+
"D'": 8,
|
|
1913
|
+
L: 9,
|
|
1914
|
+
L2: 10,
|
|
1915
|
+
"L2'": 10,
|
|
1916
|
+
"L'": 11,
|
|
1917
|
+
B: 12,
|
|
1918
|
+
B2: 13,
|
|
1919
|
+
"B2'": 13,
|
|
1920
|
+
"B'": 14
|
|
1921
|
+
};
|
|
1922
|
+
var moveMapX = {
|
|
1923
|
+
R: 0,
|
|
1924
|
+
R2: 1,
|
|
1925
|
+
"R2'": 1,
|
|
1926
|
+
"R'": 2,
|
|
1927
|
+
U: 3,
|
|
1928
|
+
U2: 4,
|
|
1929
|
+
"U2'": 4,
|
|
1930
|
+
"U'": 5,
|
|
1931
|
+
F: 6,
|
|
1932
|
+
F2: 7,
|
|
1933
|
+
"F2'": 7,
|
|
1934
|
+
"F'": 8,
|
|
1935
|
+
L: 9,
|
|
1936
|
+
L2: 10,
|
|
1937
|
+
"L2'": 10,
|
|
1938
|
+
"L'": 11,
|
|
1939
|
+
D: 12,
|
|
1940
|
+
D2: 13,
|
|
1941
|
+
"D2'": 13,
|
|
1942
|
+
"D'": 14
|
|
1943
|
+
};
|
|
1944
|
+
function isDoubleTurnNibble(nibble) {
|
|
1945
|
+
return nibble % 3 === 1;
|
|
1946
|
+
}
|
|
1947
|
+
function nibbleDuration(nibble) {
|
|
1948
|
+
return isDoubleTurnNibble(nibble) ? DOUBLE_TURN_DURATION_MS : QUANTUM_TURN_DURATION_MS;
|
|
1949
|
+
}
|
|
1950
|
+
function throwInvalidAlgNode(algNode) {
|
|
1951
|
+
console.error("invalid alg node", algNode, algNode.toString());
|
|
1952
|
+
throw new Error("invalid alg node!");
|
|
1953
|
+
}
|
|
1954
|
+
function sleep(ms) {
|
|
1955
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1956
|
+
}
|
|
1957
|
+
var GanRobot = class _GanRobot extends EventTarget {
|
|
1958
|
+
constructor(_service, server, device, statusCharacteristic, moveCharacteristic) {
|
|
1959
|
+
super();
|
|
1960
|
+
this.server = server;
|
|
1961
|
+
this.statusCharacteristic = statusCharacteristic;
|
|
1962
|
+
this.moveCharacteristic = moveCharacteristic;
|
|
1963
|
+
device.addEventListener(
|
|
1964
|
+
"gattserverdisconnected",
|
|
1965
|
+
this.onDisconnect.bind(this)
|
|
1966
|
+
);
|
|
1967
|
+
}
|
|
1968
|
+
server;
|
|
1969
|
+
statusCharacteristic;
|
|
1970
|
+
moveCharacteristic;
|
|
1971
|
+
experimentalDebugOnSend = null;
|
|
1972
|
+
experimentalDebugLog = () => {
|
|
1973
|
+
};
|
|
1974
|
+
// Because our Bluetooth connection code is set up not to know what kind of device is connecting, we put these options directly on the class.
|
|
1975
|
+
experimentalOptions = {
|
|
1976
|
+
xAngle: false,
|
|
1977
|
+
singleMoveFixHack: false,
|
|
1978
|
+
bufferQueue: 0,
|
|
1979
|
+
postSleep: 0
|
|
1980
|
+
};
|
|
1981
|
+
// We have to perform async operations before we call the constructor.
|
|
1982
|
+
static async connect(server, device) {
|
|
1983
|
+
const ganTimerService = await server.getPrimaryService(
|
|
1984
|
+
UUIDs6.ganRobotService
|
|
1985
|
+
);
|
|
1986
|
+
const statusCharacteristic = await ganTimerService.getCharacteristic(
|
|
1987
|
+
UUIDs6.statusCharacteristic
|
|
1988
|
+
);
|
|
1989
|
+
const moveCharacteristic = await ganTimerService.getCharacteristic(
|
|
1990
|
+
UUIDs6.moveCharacteristic
|
|
1991
|
+
);
|
|
1992
|
+
const timer = new _GanRobot(
|
|
1993
|
+
ganTimerService,
|
|
1994
|
+
server,
|
|
1995
|
+
device,
|
|
1996
|
+
statusCharacteristic,
|
|
1997
|
+
moveCharacteristic
|
|
1998
|
+
);
|
|
1999
|
+
return timer;
|
|
2000
|
+
}
|
|
2001
|
+
name() {
|
|
2002
|
+
return this.server.device.name;
|
|
2003
|
+
}
|
|
2004
|
+
disconnect() {
|
|
2005
|
+
this.server.disconnect();
|
|
2006
|
+
}
|
|
2007
|
+
onDisconnect() {
|
|
2008
|
+
this.dispatchEvent(new CustomEvent("disconnect"));
|
|
2009
|
+
}
|
|
2010
|
+
algNodeToNibble(algNode) {
|
|
2011
|
+
const move = algNode.as(Move);
|
|
2012
|
+
if (!move) {
|
|
2013
|
+
throwInvalidAlgNode(algNode);
|
|
2014
|
+
}
|
|
2015
|
+
const nibble = (this.experimentalOptions.xAngle ? moveMapX : moveMap2)[move.toString()] ?? null;
|
|
2016
|
+
if (nibble === null) {
|
|
2017
|
+
throwInvalidAlgNode(move);
|
|
2018
|
+
}
|
|
2019
|
+
return nibble;
|
|
2020
|
+
}
|
|
2021
|
+
async writeNibbles(nibbles) {
|
|
2022
|
+
if (nibbles.length > MAX_NIBBLES_PER_WRITE) {
|
|
2023
|
+
throw new Error(
|
|
2024
|
+
`Can only write ${MAX_NIBBLES_PER_WRITE} nibbles at a time!`
|
|
2025
|
+
);
|
|
2026
|
+
}
|
|
2027
|
+
const bytes = new Uint8Array(18);
|
|
2028
|
+
let i;
|
|
2029
|
+
for (i = 0; i < nibbles.length; i++) {
|
|
2030
|
+
const byteIdx = Math.floor(i / 2);
|
|
2031
|
+
bytes[byteIdx] += nibbles[i];
|
|
2032
|
+
if (i % 2 === 0) {
|
|
2033
|
+
bytes[byteIdx] *= 16;
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
if (nibbles.length % 2 === 1) {
|
|
2037
|
+
bytes[Math.ceil(nibbles.length / 2) - 1] += 15;
|
|
2038
|
+
}
|
|
2039
|
+
for (let i2 = Math.ceil(nibbles.length / 2); i2 < 18; i2++) {
|
|
2040
|
+
bytes[i2] = 255;
|
|
2041
|
+
}
|
|
2042
|
+
let sleepDuration = 0;
|
|
2043
|
+
for (const nibble of nibbles) {
|
|
2044
|
+
sleepDuration += nibbleDuration(nibble);
|
|
2045
|
+
}
|
|
2046
|
+
this.experimentalDebugLog("WRITING:", buf2hex3(bytes));
|
|
2047
|
+
await this.moveCharacteristic.writeValue(bytes);
|
|
2048
|
+
await sleep(sleepDuration * 0.75);
|
|
2049
|
+
while ((await this.getStatus()).movesRemaining > 0) {
|
|
2050
|
+
}
|
|
2051
|
+
await sleep(this.experimentalOptions.postSleep);
|
|
2052
|
+
}
|
|
2053
|
+
async getStatus() {
|
|
2054
|
+
const statusBytes = new Uint8Array(
|
|
2055
|
+
(await this.statusCharacteristic.readValue()).buffer
|
|
2056
|
+
);
|
|
2057
|
+
this.experimentalDebugLog("moves remaining:", statusBytes[0]);
|
|
2058
|
+
return {
|
|
2059
|
+
movesRemaining: statusBytes[0]
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
locked = false;
|
|
2063
|
+
processQueue() {
|
|
2064
|
+
}
|
|
2065
|
+
moveQueue = new Alg();
|
|
2066
|
+
// TODO: Don't let this resolve until the move is done?
|
|
2067
|
+
async queueMoves(moves) {
|
|
2068
|
+
this.moveQueue = this.moveQueue.concat(moves).experimentalSimplify({
|
|
2069
|
+
puzzleSpecificSimplifyOptions: cube3x3x3.puzzleSpecificSimplifyOptions
|
|
2070
|
+
});
|
|
2071
|
+
if (!this.locked) {
|
|
2072
|
+
try {
|
|
2073
|
+
this.locked = true;
|
|
2074
|
+
if (this.moveQueue.experimentalNumChildAlgNodes() === 1) {
|
|
2075
|
+
await sleep(this.experimentalOptions.bufferQueue);
|
|
2076
|
+
}
|
|
2077
|
+
while (this.moveQueue.experimentalNumChildAlgNodes() > 0) {
|
|
2078
|
+
let algNodes = Array.from(this.moveQueue.childAlgNodes());
|
|
2079
|
+
if (this.experimentalOptions.singleMoveFixHack && algNodes.length === 1) {
|
|
2080
|
+
const move = algNodes[0].as(Move);
|
|
2081
|
+
if (move) {
|
|
2082
|
+
if (move.amount === 2) {
|
|
2083
|
+
algNodes = [
|
|
2084
|
+
move.modified({ amount: 1 }),
|
|
2085
|
+
move.modified({ amount: 1 })
|
|
2086
|
+
];
|
|
2087
|
+
} else {
|
|
2088
|
+
algNodes = [
|
|
2089
|
+
move.modified({ amount: -move.amount }),
|
|
2090
|
+
move.modified({ amount: 2 })
|
|
2091
|
+
];
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
const splicedAlgNodes = algNodes.splice(
|
|
2096
|
+
0,
|
|
2097
|
+
MAX_NIBBLES_PER_WRITE
|
|
2098
|
+
);
|
|
2099
|
+
const nibbles = splicedAlgNodes.map(
|
|
2100
|
+
this.algNodeToNibble.bind(this)
|
|
2101
|
+
);
|
|
2102
|
+
const sending = new Alg(splicedAlgNodes);
|
|
2103
|
+
this.experimentalDebugLog("SENDING", sending.toString());
|
|
2104
|
+
if (this.experimentalDebugOnSend) {
|
|
2105
|
+
this.experimentalDebugOnSend(sending);
|
|
2106
|
+
}
|
|
2107
|
+
const write = this.writeNibbles(nibbles);
|
|
2108
|
+
this.moveQueue = new Alg(algNodes);
|
|
2109
|
+
await write;
|
|
2110
|
+
}
|
|
2111
|
+
} finally {
|
|
2112
|
+
this.locked = false;
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
async applyMoves(moves) {
|
|
2117
|
+
for (const move of moves) {
|
|
2118
|
+
const str = move.toString();
|
|
2119
|
+
if (str in (this.experimentalOptions.xAngle ? moveMapX : moveMap2)) {
|
|
2120
|
+
await this.queueMoves(new Alg([move]));
|
|
2121
|
+
} else if (move.family === (this.experimentalOptions.xAngle ? "B" : "U")) {
|
|
2122
|
+
await Promise.all([
|
|
2123
|
+
this.queueMoves(
|
|
2124
|
+
this.experimentalOptions.xAngle ? F_B_SWAP : U_D_SWAP
|
|
2125
|
+
),
|
|
2126
|
+
this.queueMoves(
|
|
2127
|
+
new Alg([
|
|
2128
|
+
move.modified({
|
|
2129
|
+
family: this.experimentalOptions.xAngle ? "F" : "D"
|
|
2130
|
+
})
|
|
2131
|
+
]).concat(
|
|
2132
|
+
this.experimentalOptions.xAngle ? F_B_UNSWAP : U_D_UNSWAP
|
|
2133
|
+
)
|
|
2134
|
+
)
|
|
2135
|
+
]);
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
};
|
|
2140
|
+
var ganTimerConfig = {
|
|
2141
|
+
connect: GanRobot.connect.bind(GanRobot),
|
|
2142
|
+
prefixes: ["GAN"],
|
|
2143
|
+
filters: [{ namePrefix: "GAN" }],
|
|
2144
|
+
optionalServices: [UUIDs6.ganRobotService]
|
|
2145
|
+
};
|
|
2146
|
+
|
|
2147
|
+
// src/cubing/bluetooth/smart-robot/index.ts
|
|
2148
|
+
var smartRobotConfigs = [ganTimerConfig];
|
|
2149
|
+
async function connectSmartRobot(options) {
|
|
2150
|
+
return bluetoothConnect(smartRobotConfigs, options);
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
// src/cubing/bluetooth/smart-timer/GanTimer.ts
|
|
2154
|
+
var UUIDs7 = {
|
|
2155
|
+
ganTimerService: "0000fff0-0000-1000-8000-00805f9b34fb",
|
|
2156
|
+
timeCharacteristic: "0000fff2-0000-1000-8000-00805f9b34fb"
|
|
2157
|
+
};
|
|
2158
|
+
var GanTimer = class _GanTimer extends EventTarget {
|
|
2159
|
+
constructor(_service, server, device, timeCharacteristic) {
|
|
2160
|
+
super();
|
|
2161
|
+
this.server = server;
|
|
2162
|
+
this.timeCharacteristic = timeCharacteristic;
|
|
2163
|
+
this.startPolling();
|
|
2164
|
+
console.log(server);
|
|
2165
|
+
device.addEventListener(
|
|
2166
|
+
"gattserverdisconnected",
|
|
2167
|
+
this.onDisconnect.bind(this)
|
|
2168
|
+
);
|
|
2169
|
+
}
|
|
2170
|
+
server;
|
|
2171
|
+
timeCharacteristic;
|
|
2172
|
+
polling = false;
|
|
2173
|
+
previousDetail = null;
|
|
2174
|
+
// We have to perform async operations before we call the constructor.
|
|
2175
|
+
static async connect(server, device) {
|
|
2176
|
+
const ganTimerService = await server.getPrimaryService(
|
|
2177
|
+
UUIDs7.ganTimerService
|
|
2178
|
+
);
|
|
2179
|
+
console.log("Service:", ganTimerService);
|
|
2180
|
+
const timeCharacteristic = await ganTimerService.getCharacteristic(
|
|
2181
|
+
UUIDs7.timeCharacteristic
|
|
2182
|
+
);
|
|
2183
|
+
console.log("Characteristic:", timeCharacteristic);
|
|
2184
|
+
const timer = new _GanTimer(
|
|
2185
|
+
ganTimerService,
|
|
2186
|
+
server,
|
|
2187
|
+
device,
|
|
2188
|
+
timeCharacteristic
|
|
2189
|
+
);
|
|
2190
|
+
return timer;
|
|
2191
|
+
}
|
|
2192
|
+
disconnect() {
|
|
2193
|
+
this.server.disconnect();
|
|
2194
|
+
}
|
|
2195
|
+
async poll() {
|
|
2196
|
+
if (!this.polling) {
|
|
2197
|
+
return;
|
|
2198
|
+
}
|
|
2199
|
+
const value = await this.getTimeCharacteristic();
|
|
2200
|
+
const detail = {
|
|
2201
|
+
currentTime: this.decodeTimeMs(value.slice(0, 4)),
|
|
2202
|
+
latestTimes: [
|
|
2203
|
+
this.decodeTimeMs(value.slice(4, 8)),
|
|
2204
|
+
this.decodeTimeMs(value.slice(8, 12)),
|
|
2205
|
+
this.decodeTimeMs(value.slice(12, 16))
|
|
2206
|
+
]
|
|
2207
|
+
};
|
|
2208
|
+
if (detail.currentTime === 0) {
|
|
2209
|
+
if (this.previousDetail && this.previousDetail.currentTime !== 0) {
|
|
2210
|
+
this.dispatchEvent(new CustomEvent("reset"));
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
if (detail.currentTime !== 0 && this.previousDetail) {
|
|
2214
|
+
if (this.previousDetail.currentTime === 0) {
|
|
2215
|
+
this.dispatchEvent(new CustomEvent("start"));
|
|
2216
|
+
}
|
|
2217
|
+
if (detail.currentTime !== this.previousDetail.currentTime) {
|
|
2218
|
+
this.dispatchEvent(new CustomEvent("update", { detail }));
|
|
2219
|
+
if (detail.currentTime === detail.latestTimes[0] && detail.latestTimes[1] === this.previousDetail.latestTimes[0] && detail.latestTimes[2] === this.previousDetail.latestTimes[1]) {
|
|
2220
|
+
this.dispatchEvent(new CustomEvent("stop", { detail }));
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
this.previousDetail = detail;
|
|
2225
|
+
void this.poll();
|
|
2226
|
+
}
|
|
2227
|
+
onDisconnect() {
|
|
2228
|
+
this.dispatchEvent(new CustomEvent("disconnect"));
|
|
2229
|
+
}
|
|
2230
|
+
async getTimeCharacteristic() {
|
|
2231
|
+
return new Uint8Array((await this.timeCharacteristic.readValue()).buffer);
|
|
2232
|
+
}
|
|
2233
|
+
async getTime() {
|
|
2234
|
+
const value = await this.getTimeCharacteristic();
|
|
2235
|
+
return this.decodeTimeMs(value.slice(0, 4));
|
|
2236
|
+
}
|
|
2237
|
+
decodeTimeMs(bytes) {
|
|
2238
|
+
return (bytes[0] * 60 + bytes[1]) * 1e3 + bytes[2] + bytes[3] * 256;
|
|
2239
|
+
}
|
|
2240
|
+
startPolling() {
|
|
2241
|
+
this.polling = true;
|
|
2242
|
+
void this.poll();
|
|
2243
|
+
}
|
|
2244
|
+
stopPolling() {
|
|
2245
|
+
this.polling = false;
|
|
2246
|
+
}
|
|
2247
|
+
};
|
|
2248
|
+
var ganTimerConfig2 = {
|
|
2249
|
+
connect: GanTimer.connect.bind(GanTimer),
|
|
2250
|
+
prefixes: ["GAN"],
|
|
2251
|
+
filters: [{ namePrefix: "GAN" }],
|
|
2252
|
+
optionalServices: [UUIDs7.ganTimerService]
|
|
2253
|
+
};
|
|
2254
|
+
|
|
2255
|
+
// src/cubing/bluetooth/smart-timer/index.ts
|
|
2256
|
+
var smartTimerConfigs = [ganTimerConfig2];
|
|
2257
|
+
async function connectSmartTimer(options) {
|
|
2258
|
+
return bluetoothConnect(smartTimerConfigs, options);
|
|
2259
|
+
}
|
|
2260
|
+
export {
|
|
2261
|
+
GanCube,
|
|
2262
|
+
GiiKERCube,
|
|
2263
|
+
GoCube,
|
|
2264
|
+
KeyboardPuzzle,
|
|
2265
|
+
connectSmartPuzzle,
|
|
2266
|
+
connectSmartRobot,
|
|
2267
|
+
connectSmartTimer,
|
|
2268
|
+
debugKeyboardConnect,
|
|
2269
|
+
enableDebugLogging
|
|
2270
|
+
};
|
|
2271
|
+
//# sourceMappingURL=index.js.map
|