@wodzik/cubecore 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +118 -0
  3. package/analyze/client.d.ts +18 -0
  4. package/analyze/client.js +42 -0
  5. package/analyze/index.d.ts +114 -0
  6. package/analyze/index.js +151 -0
  7. package/analyze/rotations.d.ts +3 -0
  8. package/analyze/rotations.js +15 -0
  9. package/analyze/roux.d.ts +67 -0
  10. package/analyze/roux.js +95 -0
  11. package/analyze/worker.d.ts +6 -0
  12. package/analyze/worker.js +31 -0
  13. package/analyze/zz.d.ts +47 -0
  14. package/analyze/zz.js +102 -0
  15. package/bld/index.d.ts +14 -0
  16. package/bld/index.js +14 -0
  17. package/bld/memo.d.ts +66 -0
  18. package/bld/memo.js +139 -0
  19. package/bld/scheme.d.ts +30 -0
  20. package/bld/scheme.js +35 -0
  21. package/bld/skin.d.ts +26 -0
  22. package/bld/skin.js +46 -0
  23. package/bld/tracker.d.ts +68 -0
  24. package/bld/tracker.js +145 -0
  25. package/bluetooth/clock.d.ts +22 -0
  26. package/bluetooth/clock.js +37 -0
  27. package/bluetooth/cubeSkins.d.ts +31 -0
  28. package/bluetooth/cubeSkins.js +32 -0
  29. package/bluetooth/gyro.d.ts +31 -0
  30. package/bluetooth/gyro.js +46 -0
  31. package/bluetooth/index.d.ts +5 -0
  32. package/bluetooth/index.js +5 -0
  33. package/bluetooth/session.d.ts +140 -0
  34. package/bluetooth/session.js +201 -0
  35. package/bluetooth/simulated.d.ts +52 -0
  36. package/bluetooth/simulated.js +80 -0
  37. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-definitions.d.ts +38 -0
  38. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-definitions.js +51 -0
  39. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-encrypter.d.ts +35 -0
  40. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-encrypter.js +70 -0
  41. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-protocol.d.ts +273 -0
  42. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-protocol.js +846 -0
  43. package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen1.d.ts +38 -0
  44. package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen1.js +340 -0
  45. package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen234-packet-validate.d.ts +11 -0
  46. package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen234-packet-validate.js +118 -0
  47. package/bluetooth/vendor/smartcube-web-bluetooth/gan-mac-salt.d.ts +2 -0
  48. package/bluetooth/vendor/smartcube-web-bluetooth/gan-mac-salt.js +15 -0
  49. package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-cube.d.ts +18 -0
  50. package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-cube.js +152 -0
  51. package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-timer.d.ts +74 -0
  52. package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-timer.js +161 -0
  53. package/bluetooth/vendor/smartcube-web-bluetooth/gan-write-queue.d.ts +7 -0
  54. package/bluetooth/vendor/smartcube-web-bluetooth/gan-write-queue.js +11 -0
  55. package/bluetooth/vendor/smartcube-web-bluetooth/gatt-characteristic-write.d.ts +2 -0
  56. package/bluetooth/vendor/smartcube-web-bluetooth/gatt-characteristic-write.js +36 -0
  57. package/bluetooth/vendor/smartcube-web-bluetooth/index.d.ts +4 -0
  58. package/bluetooth/vendor/smartcube-web-bluetooth/index.js +4 -0
  59. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/address-hints.d.ts +12 -0
  60. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/address-hints.js +122 -0
  61. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/build-picker-options.d.ts +11 -0
  62. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/build-picker-options.js +55 -0
  63. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/default-optional-services.d.ts +21 -0
  64. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/default-optional-services.js +47 -0
  65. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/device-name-scan-filters.d.ts +6 -0
  66. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/device-name-scan-filters.js +24 -0
  67. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/gatt-snapshot.d.ts +6 -0
  68. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/gatt-snapshot.js +57 -0
  69. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-candidates.d.ts +11 -0
  70. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-candidates.js +67 -0
  71. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-moyu32.d.ts +8 -0
  72. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-moyu32.js +98 -0
  73. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-qiyi.d.ts +8 -0
  74. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-qiyi.js +128 -0
  75. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/moyu32-session-crypto.d.ts +5 -0
  76. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/moyu32-session-crypto.js +60 -0
  77. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/normalize-uuid.d.ts +2 -0
  78. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/normalize-uuid.js +8 -0
  79. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/packet-sanity.d.ts +7 -0
  80. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/packet-sanity.js +142 -0
  81. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/profile-rank.d.ts +6 -0
  82. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/profile-rank.js +21 -0
  83. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/types.d.ts +32 -0
  84. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/types.js +1 -0
  85. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/ble-utils.d.ts +6 -0
  86. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/ble-utils.js +68 -0
  87. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/connect.d.ts +3 -0
  88. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/connect.js +178 -0
  89. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/cubie-cube.d.ts +16 -0
  90. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/cubie-cube.js +141 -0
  91. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/index.d.ts +11 -0
  92. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/index.js +10 -0
  93. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocol.d.ts +26 -0
  94. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocol.js +8 -0
  95. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gan.d.ts +3 -0
  96. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gan.js +265 -0
  97. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/giiker.d.ts +3 -0
  98. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/giiker.js +345 -0
  99. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gocube.d.ts +15 -0
  100. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gocube.js +382 -0
  101. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-mhc.d.ts +3 -0
  102. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-mhc.js +352 -0
  103. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-v1.d.ts +57 -0
  104. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-v1.js +243 -0
  105. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu32.d.ts +3 -0
  106. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu32.js +454 -0
  107. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/qiyi.d.ts +3 -0
  108. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/qiyi.js +424 -0
  109. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/types.d.ts +78 -0
  110. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/types.js +1 -0
  111. package/bluetooth/vendor/smartcube-web-bluetooth/utils.d.ts +45 -0
  112. package/bluetooth/vendor/smartcube-web-bluetooth/utils.js +150 -0
  113. package/cfop/caseData.d.ts +16 -0
  114. package/cfop/caseData.js +88 -0
  115. package/cfop/cases.d.ts +42 -0
  116. package/cfop/cases.js +116 -0
  117. package/cfop/f2l.d.ts +28 -0
  118. package/cfop/f2l.js +88 -0
  119. package/cfop/f2lData.d.ts +15 -0
  120. package/cfop/f2lData.js +49 -0
  121. package/cfop/index.d.ts +41 -0
  122. package/cfop/index.js +110 -0
  123. package/cfop/slots.d.ts +2 -0
  124. package/cfop/slots.js +1 -0
  125. package/core/arrows.d.ts +24 -0
  126. package/core/arrows.js +22 -0
  127. package/core/bytes.d.ts +27 -0
  128. package/core/bytes.js +145 -0
  129. package/core/centers.d.ts +32 -0
  130. package/core/centers.js +83 -0
  131. package/core/checks.d.ts +53 -0
  132. package/core/checks.js +85 -0
  133. package/core/cubies.d.ts +45 -0
  134. package/core/cubies.js +169 -0
  135. package/core/frames.d.ts +61 -0
  136. package/core/frames.js +126 -0
  137. package/core/geometry.d.ts +58 -0
  138. package/core/geometry.js +132 -0
  139. package/core/index.d.ts +19 -0
  140. package/core/index.js +19 -0
  141. package/core/masks.d.ts +32 -0
  142. package/core/masks.js +39 -0
  143. package/core/method.d.ts +35 -0
  144. package/core/method.js +30 -0
  145. package/core/moves.d.ts +48 -0
  146. package/core/moves.js +88 -0
  147. package/core/notation.d.ts +48 -0
  148. package/core/notation.js +226 -0
  149. package/core/physical.d.ts +53 -0
  150. package/core/physical.js +99 -0
  151. package/core/practice.d.ts +62 -0
  152. package/core/practice.js +87 -0
  153. package/core/sequence.d.ts +90 -0
  154. package/core/sequence.js +157 -0
  155. package/core/stages.d.ts +90 -0
  156. package/core/stages.js +21 -0
  157. package/core/state.d.ts +55 -0
  158. package/core/state.js +140 -0
  159. package/core/stateCodec.d.ts +12 -0
  160. package/core/stateCodec.js +51 -0
  161. package/core/stream.d.ts +45 -0
  162. package/core/stream.js +75 -0
  163. package/core/tracker.d.ts +62 -0
  164. package/core/tracker.js +121 -0
  165. package/element/alg.d.ts +45 -0
  166. package/element/alg.js +150 -0
  167. package/element/base.d.ts +2 -0
  168. package/element/base.js +3 -0
  169. package/element/bld.d.ts +82 -0
  170. package/element/bld.js +222 -0
  171. package/element/index.d.ts +8 -0
  172. package/element/index.js +21 -0
  173. package/element/model.d.ts +36 -0
  174. package/element/model.js +64 -0
  175. package/element/player.d.ts +196 -0
  176. package/element/player.js +680 -0
  177. package/element/practice.d.ts +75 -0
  178. package/element/practice.js +150 -0
  179. package/element/scramble.d.ts +50 -0
  180. package/element/scramble.js +107 -0
  181. package/element/sequence.d.ts +126 -0
  182. package/element/sequence.js +254 -0
  183. package/element/styles.d.ts +32 -0
  184. package/element/styles.js +196 -0
  185. package/image/cache.d.ts +21 -0
  186. package/image/cache.js +66 -0
  187. package/image/index.d.ts +2 -0
  188. package/image/index.js +2 -0
  189. package/image/png-node.d.ts +6 -0
  190. package/image/png-node.js +10 -0
  191. package/image/svg.d.ts +36 -0
  192. package/image/svg.js +215 -0
  193. package/lbl/index.d.ts +10 -0
  194. package/lbl/index.js +31 -0
  195. package/methods/index.d.ts +20 -0
  196. package/methods/index.js +33 -0
  197. package/package.json +133 -0
  198. package/petrus/index.d.ts +11 -0
  199. package/petrus/index.js +27 -0
  200. package/react/index.d.ts +176 -0
  201. package/react/index.js +228 -0
  202. package/render/arrows.d.ts +68 -0
  203. package/render/arrows.js +190 -0
  204. package/render/build/attachments.d.ts +35 -0
  205. package/render/build/attachments.js +94 -0
  206. package/render/build/models.d.ts +9 -0
  207. package/render/build/models.js +27 -0
  208. package/render/build/tiles.d.ts +48 -0
  209. package/render/build/tiles.js +194 -0
  210. package/render/gltf.d.ts +10 -0
  211. package/render/gltf.js +106 -0
  212. package/render/index.d.ts +12 -0
  213. package/render/index.js +12 -0
  214. package/render/layers.d.ts +21 -0
  215. package/render/layers.js +24 -0
  216. package/render/pictures.d.ts +46 -0
  217. package/render/pictures.js +80 -0
  218. package/render/pieceModels.d.ts +38 -0
  219. package/render/pieceModels.js +43 -0
  220. package/render/renderer.d.ts +165 -0
  221. package/render/renderer.js +647 -0
  222. package/render/tile.d.ts +114 -0
  223. package/render/tile.js +390 -0
  224. package/render/viewports.d.ts +25 -0
  225. package/render/viewports.js +26 -0
  226. package/roux/cmll.d.ts +35 -0
  227. package/roux/cmll.js +121 -0
  228. package/roux/cmllData.d.ts +12 -0
  229. package/roux/cmllData.js +49 -0
  230. package/roux/index.d.ts +52 -0
  231. package/roux/index.js +175 -0
  232. package/skin/attachments.d.ts +82 -0
  233. package/skin/attachments.js +110 -0
  234. package/skin/index.d.ts +3 -0
  235. package/skin/index.js +3 -0
  236. package/skin/shapes.d.ts +112 -0
  237. package/skin/shapes.js +147 -0
  238. package/skin/skin.d.ts +911 -0
  239. package/skin/skin.js +412 -0
  240. package/solve/client.d.ts +58 -0
  241. package/solve/client.js +56 -0
  242. package/solve/handle.d.ts +6 -0
  243. package/solve/handle.js +38 -0
  244. package/solve/index.d.ts +8 -0
  245. package/solve/index.js +7 -0
  246. package/solve/lse.d.ts +42 -0
  247. package/solve/lse.js +151 -0
  248. package/solve/pieces.d.ts +18 -0
  249. package/solve/pieces.js +24 -0
  250. package/solve/protocol.d.ts +70 -0
  251. package/solve/protocol.js +16 -0
  252. package/solve/scramble.d.ts +76 -0
  253. package/solve/scramble.js +187 -0
  254. package/solve/stage.d.ts +108 -0
  255. package/solve/stage.js +532 -0
  256. package/solve/twophase.d.ts +36 -0
  257. package/solve/twophase.js +229 -0
  258. package/solve/worker.d.ts +6 -0
  259. package/solve/worker.js +15 -0
  260. package/timeline/codec.d.ts +23 -0
  261. package/timeline/codec.js +84 -0
  262. package/timeline/index.d.ts +7 -0
  263. package/timeline/index.js +7 -0
  264. package/timeline/recording.d.ts +19 -0
  265. package/timeline/recording.js +20 -0
  266. package/timeline/replay.d.ts +68 -0
  267. package/timeline/replay.js +145 -0
  268. package/timeline/segments.d.ts +31 -0
  269. package/timeline/segments.js +39 -0
  270. package/timeline/share.d.ts +30 -0
  271. package/timeline/share.js +62 -0
  272. package/timeline/stats.d.ts +34 -0
  273. package/timeline/stats.js +56 -0
  274. package/timeline/timings.d.ts +30 -0
  275. package/timeline/timings.js +54 -0
  276. package/zz/index.d.ts +29 -0
  277. package/zz/index.js +68 -0
  278. package/zz/seeds.d.ts +9 -0
  279. package/zz/seeds.js +29 -0
@@ -0,0 +1,454 @@
1
+ import { Subject } from 'rxjs';
2
+ import { ModeOfOperation } from 'aes-js';
3
+ import { normalizeUuid } from '../attachment/normalize-uuid.js';
4
+ import { getCachedMacForDevice } from '../attachment/address-hints.js';
5
+ import { buildMoyu32MacCandidatesFromName } from '../attachment/mac-candidates.js';
6
+ import { probeMoyu32Mac } from '../attachment/mac-probe-moyu32.js';
7
+ import { registerProtocol } from '../protocol.js';
8
+ import { CubieCube, SOLVED_FACELET } from '../cubie-cube.js';
9
+ import { now, findCharacteristic, waitForAdvertisements } from '../ble-utils.js';
10
+ import { writeGattCharacteristicValue } from '../../gatt-characteristic-write.js';
11
+ const SERVICE_UUID = '0783b03e-7735-b5a0-1760-a305d2795cb0';
12
+ const CHRT_UUID_READ = '0783b03e-7735-b5a0-1760-a305d2795cb1';
13
+ const CHRT_UUID_WRITE = '0783b03e-7735-b5a0-1760-a305d2795cb2';
14
+ /** Opcode 172 + payload to enable gyro notifications (MoYu WCU). */
15
+ const ENABLE_GYRO_PAYLOAD = Object.freeze([
16
+ 172, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
17
+ ]);
18
+ const BASE_KEY = [21, 119, 58, 92, 103, 14, 45, 31, 23, 103, 42, 19, 155, 103, 82, 87];
19
+ const BASE_IV = [17, 35, 38, 37, 134, 42, 44, 59, 85, 6, 127, 49, 126, 103, 33, 87];
20
+ /**
21
+ * Parse 6 MAC octets from a manufacturer data DataView into canonical `aa:bb:…` form.
22
+ * When length >= 8, the first two bytes are treated as company ID and skipped; the next six
23
+ * are the cube address in LSB-first wire order (reversed into display order), matching key derivation.
24
+ */
25
+ function moyu32MacColonFromManufacturerDataView(dv) {
26
+ const n = dv.byteLength;
27
+ if (n < 6) {
28
+ return null;
29
+ }
30
+ const skipCid = n >= 8 ? 2 : 0;
31
+ if (n < skipCid + 6) {
32
+ return null;
33
+ }
34
+ const parts = [];
35
+ for (let i = 0; i < 6; i++) {
36
+ parts.push((dv.getUint8(skipCid + 5 - i) + 0x100).toString(16).slice(1));
37
+ }
38
+ return parts.join(':');
39
+ }
40
+ function parseMoyu32MacFromMf(mfData) {
41
+ if (!mfData) {
42
+ return null;
43
+ }
44
+ if (mfData instanceof DataView) {
45
+ return moyu32MacColonFromManufacturerDataView(mfData);
46
+ }
47
+ for (const id of mfData.keys()) {
48
+ const dataView = mfData.get(id);
49
+ if (dataView) {
50
+ const mac = moyu32MacColonFromManufacturerDataView(dataView);
51
+ if (mac) {
52
+ return mac;
53
+ }
54
+ }
55
+ }
56
+ return null;
57
+ }
58
+ class Moyu32Encrypter {
59
+ key;
60
+ iv;
61
+ constructor(macBytes) {
62
+ this.key = BASE_KEY.slice();
63
+ this.iv = BASE_IV.slice();
64
+ for (let i = 0; i < 6; i++) {
65
+ this.key[i] = (this.key[i] + macBytes[5 - i]) % 255;
66
+ this.iv[i] = (this.iv[i] + macBytes[5 - i]) % 255;
67
+ }
68
+ }
69
+ decrypt(data) {
70
+ const ret = data.slice();
71
+ const cipher = new ModeOfOperation.ecb(new Uint8Array(this.key));
72
+ if (ret.length > 16) {
73
+ const offset = ret.length - 16;
74
+ const block = cipher.decrypt(new Uint8Array(ret.slice(offset)));
75
+ for (let i = 0; i < 16; i++) {
76
+ ret[i + offset] = block[i] ^ (~~this.iv[i]);
77
+ }
78
+ }
79
+ const block = cipher.decrypt(new Uint8Array(ret.slice(0, 16)));
80
+ for (let i = 0; i < 16; i++) {
81
+ ret[i] = block[i] ^ (~~this.iv[i]);
82
+ }
83
+ return ret;
84
+ }
85
+ encrypt(data) {
86
+ const ret = data.slice();
87
+ const cipher = new ModeOfOperation.ecb(new Uint8Array(this.key));
88
+ for (let i = 0; i < 16; i++) {
89
+ ret[i] ^= ~~this.iv[i];
90
+ }
91
+ const block = cipher.encrypt(new Uint8Array(ret.slice(0, 16)));
92
+ for (let i = 0; i < 16; i++) {
93
+ ret[i] = block[i];
94
+ }
95
+ if (ret.length > 16) {
96
+ const offset = ret.length - 16;
97
+ for (let i = 0; i < 16; i++) {
98
+ ret[i + offset] ^= ~~this.iv[i];
99
+ }
100
+ const block2 = cipher.encrypt(new Uint8Array(ret.slice(offset, offset + 16)));
101
+ for (let i = 0; i < 16; i++) {
102
+ ret[i + offset] = block2[i];
103
+ }
104
+ }
105
+ return ret;
106
+ }
107
+ }
108
+ function parseFacelet(faceletBits) {
109
+ const state = [];
110
+ const faces = [2, 5, 0, 3, 4, 1]; // parse in order URFDLB instead of FBUDLR
111
+ for (let i = 0; i < 6; i++) {
112
+ const face = faceletBits.slice(faces[i] * 24, 24 + faces[i] * 24);
113
+ for (let j = 0; j < 8; j++) {
114
+ state.push("FBUDLR".charAt(parseInt(face.slice(j * 3, 3 + j * 3), 2)));
115
+ if (j === 3) {
116
+ state.push("FBUDLR".charAt(faces[i]));
117
+ }
118
+ }
119
+ }
120
+ return state.join('');
121
+ }
122
+ const MOYU32_PROTOCOL = { id: 'moyu32', name: 'MoYu32' };
123
+ class Moyu32Connection {
124
+ deviceName;
125
+ deviceMAC;
126
+ protocol = MOYU32_PROTOCOL;
127
+ capabilities = {
128
+ gyroscope: false,
129
+ battery: true,
130
+ facelets: true,
131
+ hardware: true,
132
+ reset: false
133
+ };
134
+ events$;
135
+ device;
136
+ readChrct = null;
137
+ writeChrct = null;
138
+ encrypter = null;
139
+ prevCubie = new CubieCube();
140
+ curCubie = new CubieCube();
141
+ latestFacelet = SOLVED_FACELET;
142
+ deviceTime = 0;
143
+ deviceTimeOffset = 0;
144
+ moveCnt = -1;
145
+ prevMoveCnt = -1;
146
+ lastBatteryLevel = null;
147
+ forceNextBatteryEmission = false;
148
+ batteryInterval = null;
149
+ constructor(device, mac) {
150
+ this.device = device;
151
+ this.deviceName = device.name || 'WCU_MY3';
152
+ this.deviceMAC = mac;
153
+ this.events$ = new Subject();
154
+ }
155
+ sendRequest(req) {
156
+ if (!this.writeChrct)
157
+ return Promise.resolve();
158
+ const encoded = this.encrypter ? this.encrypter.encrypt(req.slice()) : req;
159
+ return writeGattCharacteristicValue(this.writeChrct, new Uint8Array(encoded).buffer).then(() => { });
160
+ }
161
+ sendSimpleRequest(opcode) {
162
+ const req = new Array(20).fill(0);
163
+ req[0] = opcode;
164
+ return this.sendRequest(req);
165
+ }
166
+ onStateChanged = (event) => {
167
+ const value = event.target.value;
168
+ if (!value || !this.encrypter)
169
+ return;
170
+ this.parseData(value);
171
+ };
172
+ emitBatteryLevel(rawLevel, timestamp = now()) {
173
+ if (!Number.isFinite(rawLevel)) {
174
+ return;
175
+ }
176
+ const batteryLevel = Math.min(100, Math.max(0, Math.round(rawLevel)));
177
+ const forceEmission = this.forceNextBatteryEmission;
178
+ this.forceNextBatteryEmission = false;
179
+ if (!forceEmission && this.lastBatteryLevel === batteryLevel) {
180
+ return;
181
+ }
182
+ this.lastBatteryLevel = batteryLevel;
183
+ this.events$.next({
184
+ timestamp,
185
+ type: 'BATTERY',
186
+ batteryLevel,
187
+ });
188
+ }
189
+ pollBattery = () => {
190
+ void this.sendSimpleRequest(164);
191
+ };
192
+ parseData(value) {
193
+ const timestamp = now();
194
+ const raw = [];
195
+ for (let i = 0; i < value.byteLength; i++) {
196
+ raw[i] = value.getUint8(i);
197
+ }
198
+ const decoded = this.encrypter ? this.encrypter.decrypt(raw) : raw;
199
+ if ((decoded[0] | 0) === 171) {
200
+ if (!this.capabilities.gyroscope) {
201
+ this.capabilities.gyroscope = true;
202
+ }
203
+ this.parseGyroData(decoded, timestamp);
204
+ return;
205
+ }
206
+ const bits = decoded.map(b => ((b + 256) & 0xFF).toString(2).padStart(8, '0')).join('');
207
+ const msgType = parseInt(bits.slice(0, 8), 2);
208
+ if (msgType === 161) { // Hardware info
209
+ let devName = '';
210
+ for (let i = 0; i < 8; i++) {
211
+ devName += String.fromCharCode(parseInt(bits.slice(8 + i * 8, 16 + i * 8), 2));
212
+ }
213
+ const hardwareVersion = parseInt(bits.slice(88, 96), 2) + "." + parseInt(bits.slice(96, 104), 2);
214
+ const softwareVersion = parseInt(bits.slice(72, 80), 2) + "." + parseInt(bits.slice(80, 88), 2);
215
+ this.events$.next({
216
+ timestamp,
217
+ type: "HARDWARE",
218
+ hardwareName: devName.trim(),
219
+ softwareVersion,
220
+ hardwareVersion,
221
+ gyroSupported: this.capabilities.gyroscope
222
+ });
223
+ }
224
+ else if (msgType === 163) { // Facelets state
225
+ const seq = parseInt(bits.slice(152, 160), 2);
226
+ this.latestFacelet = parseFacelet(bits.slice(8, 152));
227
+ this.prevCubie.fromFacelet(this.latestFacelet);
228
+ this.moveCnt = seq;
229
+ this.prevMoveCnt = seq;
230
+ this.events$.next({
231
+ timestamp,
232
+ type: "FACELETS",
233
+ facelets: this.latestFacelet
234
+ });
235
+ }
236
+ else if (msgType === 164) { // Battery
237
+ this.emitBatteryLevel(parseInt(bits.slice(8, 16), 2), timestamp);
238
+ }
239
+ else if (msgType === 165) { // Move
240
+ this.moveCnt = parseInt(bits.slice(88, 96), 2);
241
+ if (this.moveCnt === this.prevMoveCnt || this.prevMoveCnt === -1)
242
+ return;
243
+ const prevMoves = [];
244
+ const timeOffs = [];
245
+ let invalidMove = false;
246
+ for (let i = 0; i < 5; i++) {
247
+ const m = parseInt(bits.slice(96 + i * 5, 101 + i * 5), 2);
248
+ timeOffs[i] = parseInt(bits.slice(8 + i * 16, 24 + i * 16), 2);
249
+ prevMoves[i] = "FBUDLR".charAt(m >> 1) + " '".charAt(m & 1);
250
+ if (m >= 12) {
251
+ prevMoves[i] = "U ";
252
+ invalidMove = true;
253
+ }
254
+ }
255
+ if (!invalidMove) {
256
+ const rawDelta = (this.moveCnt - this.prevMoveCnt) & 0xff;
257
+ if (rawDelta > prevMoves.length) {
258
+ console.warn('[Moyu32] lost move events', rawDelta - prevMoves.length);
259
+ }
260
+ const moveDiff = Math.min(rawDelta, prevMoves.length);
261
+ this.prevMoveCnt = this.moveCnt;
262
+ let calcTs = this.deviceTime + this.deviceTimeOffset;
263
+ for (let i = moveDiff - 1; i >= 0; i--) {
264
+ calcTs += timeOffs[i];
265
+ }
266
+ if (!this.deviceTime || Math.abs(timestamp - calcTs) > 2000) {
267
+ this.deviceTime += timestamp - calcTs;
268
+ }
269
+ for (let i = moveDiff - 1; i >= 0; i--) {
270
+ const moveNotation = prevMoves[i].trim();
271
+ const m = "URFDLB".indexOf(moveNotation[0]) * 3 + " 2'".indexOf(moveNotation[1] || ' ');
272
+ CubieCube.CubeMult(this.prevCubie, CubieCube.moveCube[m], this.curCubie);
273
+ this.deviceTime += timeOffs[i];
274
+ const face = Math.floor(m / 3);
275
+ const direction = m % 3;
276
+ this.events$.next({
277
+ timestamp,
278
+ type: "MOVE",
279
+ face,
280
+ direction,
281
+ move: moveNotation,
282
+ localTimestamp: i === 0 ? timestamp : null,
283
+ cubeTimestamp: this.deviceTime
284
+ });
285
+ this.events$.next({
286
+ timestamp,
287
+ type: "FACELETS",
288
+ facelets: this.curCubie.toFaceCube()
289
+ });
290
+ const tmp = this.curCubie;
291
+ this.curCubie = this.prevCubie;
292
+ this.prevCubie = tmp;
293
+ }
294
+ this.deviceTimeOffset = timestamp - this.deviceTime;
295
+ }
296
+ }
297
+ }
298
+ parseGyroData(decoded, timestamp) {
299
+ const dv = new DataView(Uint8Array.from(decoded).buffer);
300
+ const scale = 1073741824; // 2^30
301
+ let w = dv.getInt32(1, true) / scale;
302
+ let x = dv.getInt32(5, true) / scale;
303
+ let y = dv.getInt32(9, true) / scale;
304
+ let z = dv.getInt32(13, true) / scale;
305
+ const len = Math.hypot(w, x, y, z);
306
+ if (len > 0) {
307
+ w /= len;
308
+ x /= len;
309
+ y /= len;
310
+ z /= len;
311
+ }
312
+ this.events$.next({
313
+ timestamp,
314
+ type: "GYRO",
315
+ quaternion: { x, y, z, w }
316
+ });
317
+ }
318
+ onDisconnect = () => {
319
+ this.device.removeEventListener('gattserverdisconnected', this.onDisconnect);
320
+ this.lastBatteryLevel = null;
321
+ this.forceNextBatteryEmission = false;
322
+ if (this.batteryInterval) {
323
+ clearInterval(this.batteryInterval);
324
+ this.batteryInterval = null;
325
+ }
326
+ this.events$.next({ timestamp: now(), type: "DISCONNECT" });
327
+ this.events$.complete();
328
+ };
329
+ async init() {
330
+ this.device.addEventListener('gattserverdisconnected', this.onDisconnect);
331
+ const gatt = await this.device.gatt.connect();
332
+ const service = await gatt.getPrimaryService(SERVICE_UUID);
333
+ const chrcts = await service.getCharacteristics();
334
+ this.readChrct = findCharacteristic(chrcts, CHRT_UUID_READ);
335
+ this.writeChrct = findCharacteristic(chrcts, CHRT_UUID_WRITE);
336
+ if (!this.readChrct || !this.writeChrct) {
337
+ throw new Error(!this.readChrct
338
+ ? '[Moyu32] Cannot find required characteristics'
339
+ : '[Moyu32] Cannot find write characteristic');
340
+ }
341
+ this.readChrct.addEventListener('characteristicvaluechanged', this.onStateChanged);
342
+ await this.readChrct.startNotifications();
343
+ // Initialize encryption with MAC
344
+ const macBytes = this.deviceMAC.split(/[:-\s]+/).map(c => parseInt(c, 16));
345
+ this.encrypter = new Moyu32Encrypter(macBytes);
346
+ await this.sendSimpleRequest(161); // Request cube info
347
+ await this.sendSimpleRequest(163); // Request cube status (facelets)
348
+ await this.sendSimpleRequest(164); // Request battery level
349
+ // Some MoYu32 variants require an extra request burst before
350
+ // gyro enable + steady-state status updates begin.
351
+ await this.sendSimpleRequest(161);
352
+ await this.sendSimpleRequest(163);
353
+ await this.sendSimpleRequest(164);
354
+ this.batteryInterval = setInterval(this.pollBattery, 60_000);
355
+ await this.sendRequest(Array.from(ENABLE_GYRO_PAYLOAD));
356
+ await this.sendSimpleRequest(163); // Refresh cube status after enabling gyro notifications
357
+ }
358
+ async sendCommand(command) {
359
+ switch (command.type) {
360
+ case "REQUEST_HARDWARE":
361
+ await this.sendSimpleRequest(161);
362
+ break;
363
+ case "REQUEST_FACELETS":
364
+ await this.sendSimpleRequest(163);
365
+ break;
366
+ case "REQUEST_BATTERY":
367
+ this.forceNextBatteryEmission = true;
368
+ await this.sendSimpleRequest(164);
369
+ break;
370
+ }
371
+ }
372
+ async disconnect() {
373
+ if (this.readChrct) {
374
+ this.readChrct.removeEventListener('characteristicvaluechanged', this.onStateChanged);
375
+ await this.readChrct.stopNotifications().catch(() => { });
376
+ this.readChrct = null;
377
+ }
378
+ this.lastBatteryLevel = null;
379
+ this.forceNextBatteryEmission = false;
380
+ if (this.batteryInterval) {
381
+ clearInterval(this.batteryInterval);
382
+ this.batteryInterval = null;
383
+ }
384
+ this.writeChrct = null;
385
+ this.device.removeEventListener('gattserverdisconnected', this.onDisconnect);
386
+ this.events$.next({ timestamp: now(), type: "DISCONNECT" });
387
+ this.events$.complete();
388
+ if (this.device.gatt?.connected) {
389
+ this.device.gatt.disconnect();
390
+ }
391
+ }
392
+ }
393
+ async function connectMoyu32Device(device, macProvider, context) {
394
+ let mac = parseMoyu32MacFromMf(context?.advertisementManufacturerData ?? null);
395
+ mac = mac || getCachedMacForDevice(device);
396
+ if (!mac && macProvider) {
397
+ const r = await macProvider(device, false);
398
+ if (r) {
399
+ mac = r;
400
+ }
401
+ }
402
+ if (!mac) {
403
+ const mfData = await waitForAdvertisements(device, context?.enableAddressSearch ? 8000 : 5000);
404
+ mac = parseMoyu32MacFromMf(mfData);
405
+ }
406
+ if (!mac && context?.enableAddressSearch) {
407
+ const candidates = buildMoyu32MacCandidatesFromName(device.name);
408
+ const timeoutMs = 2000;
409
+ for (let i = 0; i < candidates.length; i++) {
410
+ if (context.signal?.aborted) {
411
+ break;
412
+ }
413
+ context.onStatus?.(`Testing address (${i + 1}/${candidates.length})…`);
414
+ try {
415
+ if (await probeMoyu32Mac(device, candidates[i], {
416
+ timeoutMs,
417
+ signal: context.signal,
418
+ })) {
419
+ mac = candidates[i];
420
+ break;
421
+ }
422
+ }
423
+ catch {
424
+ /* try next */
425
+ }
426
+ }
427
+ }
428
+ if (!mac && macProvider) {
429
+ const r = await macProvider(device, true);
430
+ if (r) {
431
+ mac = r;
432
+ }
433
+ }
434
+ if (!mac) {
435
+ throw new Error('Unable to determine MoYu32 cube MAC address');
436
+ }
437
+ const conn = new Moyu32Connection(device, mac);
438
+ await conn.init();
439
+ return conn;
440
+ }
441
+ const moyu32Protocol = {
442
+ nameFilters: [{ namePrefix: '^S' }, { namePrefix: 'WCU_' }, { namePrefix: 'WCU_MY3' }],
443
+ optionalServices: [SERVICE_UUID],
444
+ matchesDevice(device) {
445
+ const name = device.name || '';
446
+ return name.startsWith('^S') || name.startsWith('WCU_') || name.startsWith('WCU_MY3');
447
+ },
448
+ gattAffinity(serviceUuids, _device) {
449
+ return serviceUuids.has(normalizeUuid(SERVICE_UUID)) ? 110 : 0;
450
+ },
451
+ connect: connectMoyu32Device
452
+ };
453
+ registerProtocol(moyu32Protocol);
454
+ export { moyu32Protocol };
@@ -0,0 +1,3 @@
1
+ import { SmartCubeProtocol } from '../protocol.js';
2
+ declare const qiyiProtocol: SmartCubeProtocol;
3
+ export { qiyiProtocol };