@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.
Files changed (136) hide show
  1. package/README.md +120 -0
  2. package/alg/index.d.ts +1 -0
  3. package/bluetooth/index.d.ts +1 -0
  4. package/dist/bin/chunks/chunk-22VWCGBC.js +13 -0
  5. package/dist/bin/chunks/chunk-22VWCGBC.js.map +7 -0
  6. package/dist/bin/order.js +59 -0
  7. package/dist/bin/order.js.map +7 -0
  8. package/dist/bin/puzzle-geometry-bin.js +407 -0
  9. package/dist/bin/puzzle-geometry-bin.js.map +7 -0
  10. package/dist/bin/scramble.js +188 -0
  11. package/dist/bin/scramble.js.map +7 -0
  12. package/dist/bin/svg.js +56 -0
  13. package/dist/bin/svg.js.map +7 -0
  14. package/dist/lib/cubing/alg/index.d.ts +2 -0
  15. package/dist/lib/cubing/alg/index.js +43 -0
  16. package/dist/lib/cubing/alg/index.js.map +7 -0
  17. package/dist/lib/cubing/bluetooth/index.d.ts +189 -0
  18. package/dist/lib/cubing/bluetooth/index.js +2271 -0
  19. package/dist/lib/cubing/bluetooth/index.js.map +7 -0
  20. package/dist/lib/cubing/chunks/big-puzzle-orientation-BA6QHO5C.js +68 -0
  21. package/dist/lib/cubing/chunks/big-puzzle-orientation-BA6QHO5C.js.map +7 -0
  22. package/dist/lib/cubing/chunks/chunk-2VJB4TMH.js +1774 -0
  23. package/dist/lib/cubing/chunks/chunk-2VJB4TMH.js.map +7 -0
  24. package/dist/lib/cubing/chunks/chunk-52ZXJEUO.js +665 -0
  25. package/dist/lib/cubing/chunks/chunk-52ZXJEUO.js.map +7 -0
  26. package/dist/lib/cubing/chunks/chunk-56VZAEPF.js +274 -0
  27. package/dist/lib/cubing/chunks/chunk-56VZAEPF.js.map +7 -0
  28. package/dist/lib/cubing/chunks/chunk-7GUL3OBQ.js +9 -0
  29. package/dist/lib/cubing/chunks/chunk-7GUL3OBQ.js.map +7 -0
  30. package/dist/lib/cubing/chunks/chunk-7L7BJSIM.js +21 -0
  31. package/dist/lib/cubing/chunks/chunk-7L7BJSIM.js.map +7 -0
  32. package/dist/lib/cubing/chunks/chunk-AESPJKN6.js +265 -0
  33. package/dist/lib/cubing/chunks/chunk-AESPJKN6.js.map +7 -0
  34. package/dist/lib/cubing/chunks/chunk-C2Q347XS.js +89 -0
  35. package/dist/lib/cubing/chunks/chunk-C2Q347XS.js.map +7 -0
  36. package/dist/lib/cubing/chunks/chunk-CGBMJBU3.js +1195 -0
  37. package/dist/lib/cubing/chunks/chunk-CGBMJBU3.js.map +7 -0
  38. package/dist/lib/cubing/chunks/chunk-HZQONEMD.js +249 -0
  39. package/dist/lib/cubing/chunks/chunk-HZQONEMD.js.map +7 -0
  40. package/dist/lib/cubing/chunks/chunk-IAGCVPL3.js +1069 -0
  41. package/dist/lib/cubing/chunks/chunk-IAGCVPL3.js.map +7 -0
  42. package/dist/lib/cubing/chunks/chunk-NAPITA3L.js +1 -0
  43. package/dist/lib/cubing/chunks/chunk-NAPITA3L.js.map +7 -0
  44. package/dist/lib/cubing/chunks/chunk-XPDJUSZ7.js +1876 -0
  45. package/dist/lib/cubing/chunks/chunk-XPDJUSZ7.js.map +7 -0
  46. package/dist/lib/cubing/chunks/chunk-YSI6PF35.js +619 -0
  47. package/dist/lib/cubing/chunks/chunk-YSI6PF35.js.map +7 -0
  48. package/dist/lib/cubing/chunks/inside-LG2EAKCL.js +557 -0
  49. package/dist/lib/cubing/chunks/inside-LG2EAKCL.js.map +7 -0
  50. package/dist/lib/cubing/chunks/puzzles-dynamic-2x2x2-JYJJXB4V.js +142 -0
  51. package/dist/lib/cubing/chunks/puzzles-dynamic-2x2x2-JYJJXB4V.js.map +7 -0
  52. package/dist/lib/cubing/chunks/puzzles-dynamic-3x3x3-FYXD7SIU.js +365 -0
  53. package/dist/lib/cubing/chunks/puzzles-dynamic-3x3x3-FYXD7SIU.js.map +7 -0
  54. package/dist/lib/cubing/chunks/puzzles-dynamic-4x4x4-AIGABOAR.js +132 -0
  55. package/dist/lib/cubing/chunks/puzzles-dynamic-4x4x4-AIGABOAR.js.map +7 -0
  56. package/dist/lib/cubing/chunks/puzzles-dynamic-5x5x5-JUNRX27W.js +277 -0
  57. package/dist/lib/cubing/chunks/puzzles-dynamic-5x5x5-JUNRX27W.js.map +7 -0
  58. package/dist/lib/cubing/chunks/puzzles-dynamic-clock-USGQTDPO.js +836 -0
  59. package/dist/lib/cubing/chunks/puzzles-dynamic-clock-USGQTDPO.js.map +7 -0
  60. package/dist/lib/cubing/chunks/puzzles-dynamic-fto-WJHMLYCJ.js +100 -0
  61. package/dist/lib/cubing/chunks/puzzles-dynamic-fto-WJHMLYCJ.js.map +7 -0
  62. package/dist/lib/cubing/chunks/puzzles-dynamic-megaminx-2LVHIDL4.js +224 -0
  63. package/dist/lib/cubing/chunks/puzzles-dynamic-megaminx-2LVHIDL4.js.map +7 -0
  64. package/dist/lib/cubing/chunks/puzzles-dynamic-pyraminx-7UWX4HHI.js +93 -0
  65. package/dist/lib/cubing/chunks/puzzles-dynamic-pyraminx-7UWX4HHI.js.map +7 -0
  66. package/dist/lib/cubing/chunks/puzzles-dynamic-square1-PQEKBPFN.js +842 -0
  67. package/dist/lib/cubing/chunks/puzzles-dynamic-square1-PQEKBPFN.js.map +7 -0
  68. package/dist/lib/cubing/chunks/search-dynamic-sgs-2x2x2-XFKQ4VJS.js +125 -0
  69. package/dist/lib/cubing/chunks/search-dynamic-sgs-2x2x2-XFKQ4VJS.js.map +7 -0
  70. package/dist/lib/cubing/chunks/search-dynamic-sgs-megaminx-ZDOOLEDW.js +1564 -0
  71. package/dist/lib/cubing/chunks/search-dynamic-sgs-megaminx-ZDOOLEDW.js.map +7 -0
  72. package/dist/lib/cubing/chunks/search-dynamic-sgs-pyraminx-W6UJWBYY.js +108 -0
  73. package/dist/lib/cubing/chunks/search-dynamic-sgs-pyraminx-W6UJWBYY.js.map +7 -0
  74. package/dist/lib/cubing/chunks/search-dynamic-sgs-skewb-FXGKV4KD.js +125 -0
  75. package/dist/lib/cubing/chunks/search-dynamic-sgs-skewb-FXGKV4KD.js.map +7 -0
  76. package/dist/lib/cubing/chunks/search-dynamic-sgs-unofficial-YK6HA7BK.js +644 -0
  77. package/dist/lib/cubing/chunks/search-dynamic-sgs-unofficial-YK6HA7BK.js.map +7 -0
  78. package/dist/lib/cubing/chunks/search-dynamic-solve-3x3x3-DLMAGPXS.js +2549 -0
  79. package/dist/lib/cubing/chunks/search-dynamic-solve-3x3x3-DLMAGPXS.js.map +7 -0
  80. package/dist/lib/cubing/chunks/search-dynamic-solve-4x4x4-NONXQHOJ.js +2923 -0
  81. package/dist/lib/cubing/chunks/search-dynamic-solve-4x4x4-NONXQHOJ.js.map +7 -0
  82. package/dist/lib/cubing/chunks/search-dynamic-solve-fto-3GQ2HLSW.js +1405 -0
  83. package/dist/lib/cubing/chunks/search-dynamic-solve-fto-3GQ2HLSW.js.map +7 -0
  84. package/dist/lib/cubing/chunks/search-worker-entry.js +19 -0
  85. package/dist/lib/cubing/chunks/search-worker-entry.js.map +7 -0
  86. package/dist/lib/cubing/chunks/square1-stickerings-W2ZLZWIN.js +73 -0
  87. package/dist/lib/cubing/chunks/square1-stickerings-W2ZLZWIN.js.map +7 -0
  88. package/dist/lib/cubing/chunks/twips-2RNTWYNZ.js +433 -0
  89. package/dist/lib/cubing/chunks/twips-2RNTWYNZ.js.map +7 -0
  90. package/dist/lib/cubing/chunks/twips_wasm_bg-TJNYGA2C-23XTPLIK.js +10 -0
  91. package/dist/lib/cubing/chunks/twips_wasm_bg-TJNYGA2C-23XTPLIK.js.map +7 -0
  92. package/dist/lib/cubing/chunks/twisty-dynamic-3d-JO6OGKOF.js +2424 -0
  93. package/dist/lib/cubing/chunks/twisty-dynamic-3d-JO6OGKOF.js.map +7 -0
  94. package/dist/lib/cubing/index-BX6f56uo.d.ts +76 -0
  95. package/dist/lib/cubing/index-CcRu94bx.d.ts +2622 -0
  96. package/dist/lib/cubing/kpuzzle/index.d.ts +2 -0
  97. package/dist/lib/cubing/kpuzzle/index.js +12 -0
  98. package/dist/lib/cubing/kpuzzle/index.js.map +7 -0
  99. package/dist/lib/cubing/notation/index.d.ts +32 -0
  100. package/dist/lib/cubing/notation/index.js +20 -0
  101. package/dist/lib/cubing/notation/index.js.map +7 -0
  102. package/dist/lib/cubing/outside-BCzgSYQ_.d.ts +38 -0
  103. package/dist/lib/cubing/protocol/index.d.ts +28 -0
  104. package/dist/lib/cubing/protocol/index.js +20 -0
  105. package/dist/lib/cubing/protocol/index.js.map +7 -0
  106. package/dist/lib/cubing/puzzle-geometry/index.d.ts +2 -0
  107. package/dist/lib/cubing/puzzle-geometry/index.js +5024 -0
  108. package/dist/lib/cubing/puzzle-geometry/index.js.map +7 -0
  109. package/dist/lib/cubing/puzzles/index.d.ts +2 -0
  110. package/dist/lib/cubing/puzzles/index.js +23 -0
  111. package/dist/lib/cubing/puzzles/index.js.map +7 -0
  112. package/dist/lib/cubing/scramble/index.d.ts +2 -0
  113. package/dist/lib/cubing/scramble/index.js +16 -0
  114. package/dist/lib/cubing/scramble/index.js.map +7 -0
  115. package/dist/lib/cubing/search/index.d.ts +7 -0
  116. package/dist/lib/cubing/search/index.js +37 -0
  117. package/dist/lib/cubing/search/index.js.map +7 -0
  118. package/dist/lib/cubing/stream/index.d.ts +2 -0
  119. package/dist/lib/cubing/stream/index.js +111 -0
  120. package/dist/lib/cubing/stream/index.js.map +7 -0
  121. package/dist/lib/cubing/twisty/index.d.ts +2 -0
  122. package/dist/lib/cubing/twisty/index.js +6236 -0
  123. package/dist/lib/cubing/twisty/index.js.map +7 -0
  124. package/experimental-json-schema/kpuzzle/KPatternData.schema.json +34 -0
  125. package/experimental-json-schema/kpuzzle/KPuzzleDefinition.schema.json +97 -0
  126. package/experimental-json-schema/kpuzzle/KTransformationData.schema.json +27 -0
  127. package/kpuzzle/index.d.ts +1 -0
  128. package/notation/index.d.ts +1 -0
  129. package/package.json +146 -0
  130. package/protocol/index.d.ts +1 -0
  131. package/puzzle-geometry/index.d.ts +1 -0
  132. package/puzzles/index.d.ts +1 -0
  133. package/scramble/index.d.ts +1 -0
  134. package/search/index.d.ts +1 -0
  135. package/stream/index.d.ts +1 -0
  136. 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