@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,382 @@
1
+ import { Subject } from 'rxjs';
2
+ import { normalizeUuid } from '../attachment/normalize-uuid.js';
3
+ import { registerProtocol } from '../protocol.js';
4
+ import { CubieCube, SOLVED_FACELET } from '../cubie-cube.js';
5
+ import { now } from '../ble-utils.js';
6
+ import { writeGattCharacteristicValue } from '../../gatt-characteristic-write.js';
7
+ const UUID_SUFFIX = '-b5a3-f393-e0a9-e50e24dcca9e';
8
+ const SERVICE_UUID = '6e400001' + UUID_SUFFIX;
9
+ const CHRCT_UUID_WRITE = '6e400002' + UUID_SUFFIX;
10
+ const CHRCT_UUID_READ = '6e400003' + UUID_SUFFIX;
11
+ const WRITE_BATTERY = 50;
12
+ const WRITE_STATE = 51;
13
+ const WRITE_RESET = 53;
14
+ /** Enable MsgOrientation (3D tracking). Rubik's Connected / GoCube X omit IMU; only classic GoCube uses this. */
15
+ const WRITE_ENABLE_ORIENTATION = 0x38;
16
+ const INITIAL_STATE_TIMEOUT_MS = 5000;
17
+ /** True for classic GoCube (incl. `GoCube_*`); false for Rubik's Connected and GoCube X (same UART, no gyro). */
18
+ function goCubeDeviceSupportsGyro(deviceName) {
19
+ if (!deviceName.startsWith('GoCube')) {
20
+ return false;
21
+ }
22
+ if (deviceName.startsWith('GoCubeX')) {
23
+ return false;
24
+ }
25
+ return true;
26
+ }
27
+ /** Sum of bytes 0..(checksum-1) mod 256 equals checksum byte (immediately before CRLF). */
28
+ function gocubeChecksumValid(value) {
29
+ if (value.byteLength < 7) {
30
+ return false;
31
+ }
32
+ let sum = 0;
33
+ for (let i = 0; i <= value.byteLength - 4; i++) {
34
+ sum += value.getUint8(i);
35
+ }
36
+ return (sum & 0xff) === value.getUint8(value.byteLength - 3);
37
+ }
38
+ /**
39
+ * MsgOrientation payload: ASCII decimals `x#y#z#w` (see public GoCube UART docs). Normalize to a unit quaternion.
40
+ * Integer components are scaled together so normalization yields the physical orientation.
41
+ *
42
+ * Wire `(rx,ry,rz,rw)` normalized to `(nx,ny,nz,nw)` then mapped to `(nx, -nz, -ny, nw)`.
43
+ */
44
+ export function parseGoCubeOrientationPayload(payloadUtf8) {
45
+ const parts = payloadUtf8.split('#');
46
+ if (parts.length !== 4) {
47
+ return null;
48
+ }
49
+ const rx = Number.parseInt(parts[0].trim(), 10);
50
+ const ry = Number.parseInt(parts[1].trim(), 10);
51
+ const rz = Number.parseInt(parts[2].trim(), 10);
52
+ const rw = Number.parseInt(parts[3].trim(), 10);
53
+ if (![rx, ry, rz, rw].every((n) => Number.isFinite(n))) {
54
+ return null;
55
+ }
56
+ const len = Math.hypot(rx, ry, rz, rw);
57
+ if (len === 0) {
58
+ return null;
59
+ }
60
+ const nx = rx / len;
61
+ const ny = ry / len;
62
+ const nz = rz / len;
63
+ const nw = rw / len;
64
+ return { x: nx, y: -nz, z: -ny, w: nw };
65
+ }
66
+ const AXIS_PERM = [5, 2, 0, 3, 1, 4];
67
+ const FACE_PERM = [0, 1, 2, 5, 8, 7, 6, 3];
68
+ const FACE_OFFSET = [0, 0, 6, 2, 0, 0];
69
+ /** Physical opposite faces in URFDLB axis order (U↔D, R↔L, F↔B). */
70
+ const OPPOSITE_AXIS = [3, 4, 5, 0, 1, 2];
71
+ const GOCUBE_PROTOCOL = { id: 'gocube', name: 'GoCube' };
72
+ class GoCubeConnection {
73
+ deviceName;
74
+ deviceMAC;
75
+ protocol = GOCUBE_PROTOCOL;
76
+ capabilities;
77
+ events$;
78
+ device;
79
+ readChrct = null;
80
+ writeChrct = null;
81
+ curCubie = new CubieCube();
82
+ prevCubie = new CubieCube();
83
+ moveCntFree = 100;
84
+ lastBatteryLevel = null;
85
+ forceNextBatteryEmission = false;
86
+ batteryInterval = null;
87
+ /** Last decoded move (axis + direction bit) for short type-1 frames that omit a full pair of bytes. */
88
+ lastMoveMeta = null;
89
+ /** First full-state (type 2) after connect: defer FACELETS until `init` finishes so subscribers never miss it. */
90
+ awaitingInitialState = false;
91
+ resolveInitialState;
92
+ constructor(device, name, gyroSupported) {
93
+ this.device = device;
94
+ this.deviceName = name;
95
+ this.deviceMAC = '';
96
+ this.capabilities = {
97
+ gyroscope: gyroSupported,
98
+ battery: true,
99
+ facelets: true,
100
+ hardware: true,
101
+ reset: true
102
+ };
103
+ this.events$ = new Subject();
104
+ }
105
+ onStateChanged = (event) => {
106
+ const value = event.target.value;
107
+ if (!value)
108
+ return;
109
+ this.parseData(value);
110
+ };
111
+ applySingleMove(timestamp, axis, dirBit) {
112
+ const power = [0, 2][dirBit];
113
+ const m = axis * 3 + power;
114
+ const moveStr = ("URFDLB".charAt(axis) + " 2'".charAt(power)).trim();
115
+ CubieCube.CubeMult(this.prevCubie, CubieCube.moveCube[m], this.curCubie);
116
+ const facelet = this.curCubie.toFaceCube();
117
+ this.events$.next({
118
+ timestamp,
119
+ type: "MOVE",
120
+ face: axis,
121
+ direction: power === 0 ? 0 : 1,
122
+ move: moveStr,
123
+ localTimestamp: timestamp,
124
+ cubeTimestamp: null
125
+ });
126
+ this.events$.next({
127
+ timestamp,
128
+ type: "FACELETS",
129
+ facelets: facelet
130
+ });
131
+ const tmp = this.curCubie;
132
+ this.curCubie = this.prevCubie;
133
+ this.prevCubie = tmp;
134
+ if (++this.moveCntFree > 20) {
135
+ this.moveCntFree = 0;
136
+ this.writeChrct &&
137
+ writeGattCharacteristicValue(this.writeChrct, new Uint8Array([WRITE_STATE]).buffer).catch(() => { });
138
+ }
139
+ }
140
+ emitBatteryLevel(rawLevel, timestamp = now()) {
141
+ if (!Number.isFinite(rawLevel)) {
142
+ return;
143
+ }
144
+ const batteryLevel = Math.min(100, Math.max(0, Math.round(rawLevel)));
145
+ const forceEmission = this.forceNextBatteryEmission;
146
+ this.forceNextBatteryEmission = false;
147
+ if (!forceEmission && this.lastBatteryLevel === batteryLevel) {
148
+ return;
149
+ }
150
+ this.lastBatteryLevel = batteryLevel;
151
+ this.events$.next({
152
+ timestamp,
153
+ type: 'BATTERY',
154
+ batteryLevel,
155
+ });
156
+ }
157
+ pollBattery = () => {
158
+ if (!this.writeChrct) {
159
+ return;
160
+ }
161
+ writeGattCharacteristicValue(this.writeChrct, new Uint8Array([WRITE_BATTERY]).buffer).catch(() => { });
162
+ };
163
+ parseData(value) {
164
+ const timestamp = now();
165
+ if (value.byteLength < 4)
166
+ return;
167
+ if (value.getUint8(0) !== 0x2a ||
168
+ value.getUint8(value.byteLength - 2) !== 0x0d ||
169
+ value.getUint8(value.byteLength - 1) !== 0x0a) {
170
+ return;
171
+ }
172
+ // Full frames include a checksum byte before CRLF; short type-1 move frames may be smaller.
173
+ if (value.byteLength >= 7 && !gocubeChecksumValid(value)) {
174
+ return;
175
+ }
176
+ const msgType = value.getUint8(2);
177
+ const msgLen = value.byteLength - 6;
178
+ if (msgType === 3) {
179
+ // MsgOrientation: ASCII x#y#z#w between byte 3 and checksum.
180
+ if (!this.capabilities.gyroscope || value.byteLength < 8) {
181
+ return;
182
+ }
183
+ const end = value.byteLength - 3;
184
+ const payload = new Uint8Array(value.buffer, value.byteOffset + 3, end - 3);
185
+ const text = new TextDecoder('utf-8', { fatal: false }).decode(payload);
186
+ const q = parseGoCubeOrientationPayload(text);
187
+ if (!q) {
188
+ return;
189
+ }
190
+ this.events$.next({
191
+ timestamp,
192
+ type: 'GYRO',
193
+ quaternion: q
194
+ });
195
+ return;
196
+ }
197
+ if (msgType === 1) { // Move
198
+ // Firmware may send a truncated type-1 frame (< 8 bytes): treat as opposite-face turn,
199
+ // mirroring the last full move.
200
+ if (value.byteLength < 8) {
201
+ if (this.lastMoveMeta) {
202
+ const oppAxis = OPPOSITE_AXIS[this.lastMoveMeta.axis];
203
+ const newDirBit = 1 - this.lastMoveMeta.dirBit;
204
+ this.applySingleMove(timestamp, oppAxis, newDirBit);
205
+ this.lastMoveMeta = { axis: oppAxis, dirBit: newDirBit };
206
+ }
207
+ return;
208
+ }
209
+ for (let i = 0; i < msgLen; i += 2) {
210
+ const axis = AXIS_PERM[value.getUint8(3 + i) >> 1];
211
+ const dirBit = value.getUint8(3 + i) & 1;
212
+ this.lastMoveMeta = { axis, dirBit };
213
+ this.applySingleMove(timestamp, axis, dirBit);
214
+ }
215
+ }
216
+ else if (msgType === 2) { // Cube state
217
+ // Full-cube state is six 9-sticker faces in wire order; unpack with AXIS_PERM/FACE_PERM.
218
+ const facelet = [];
219
+ for (let a = 0; a < 6; a++) {
220
+ const axis = AXIS_PERM[a] * 9;
221
+ const aoff = FACE_OFFSET[a];
222
+ facelet[axis + 4] = "BFUDRL".charAt(value.getUint8(3 + a * 9));
223
+ for (let i = 0; i < 8; i++) {
224
+ facelet[axis + FACE_PERM[(i + aoff) % 8]] = "BFUDRL".charAt(value.getUint8(3 + a * 9 + i + 1));
225
+ }
226
+ }
227
+ const newFacelet = facelet.join('');
228
+ const curFacelet = this.prevCubie.toFaceCube();
229
+ if (newFacelet !== curFacelet) {
230
+ this.curCubie.fromFacelet(newFacelet);
231
+ const tmp = this.curCubie;
232
+ this.curCubie = this.prevCubie;
233
+ this.prevCubie = tmp;
234
+ }
235
+ if (this.awaitingInitialState && this.resolveInitialState) {
236
+ const done = this.resolveInitialState;
237
+ this.resolveInitialState = undefined;
238
+ this.awaitingInitialState = false;
239
+ done();
240
+ return;
241
+ }
242
+ this.events$.next({
243
+ timestamp,
244
+ type: "FACELETS",
245
+ facelets: this.prevCubie.toFaceCube()
246
+ });
247
+ }
248
+ else if (msgType === 5) { // Battery
249
+ this.emitBatteryLevel(value.getUint8(3), timestamp);
250
+ }
251
+ }
252
+ emitHardwareEvent() {
253
+ this.events$.next({
254
+ timestamp: now(),
255
+ type: "HARDWARE",
256
+ hardwareName: this.deviceName,
257
+ gyroSupported: this.capabilities.gyroscope
258
+ });
259
+ }
260
+ onDisconnect = () => {
261
+ this.device.removeEventListener('gattserverdisconnected', this.onDisconnect);
262
+ this.lastBatteryLevel = null;
263
+ this.forceNextBatteryEmission = false;
264
+ if (this.batteryInterval) {
265
+ clearInterval(this.batteryInterval);
266
+ this.batteryInterval = null;
267
+ }
268
+ this.events$.next({ timestamp: now(), type: "DISCONNECT" });
269
+ this.events$.complete();
270
+ };
271
+ async init() {
272
+ this.device.addEventListener('gattserverdisconnected', this.onDisconnect);
273
+ const gatt = await this.device.gatt.connect();
274
+ const service = await gatt.getPrimaryService(SERVICE_UUID);
275
+ this.writeChrct = await service.getCharacteristic(CHRCT_UUID_WRITE);
276
+ this.readChrct = await service.getCharacteristic(CHRCT_UUID_READ);
277
+ await this.readChrct.startNotifications();
278
+ this.readChrct.addEventListener('characteristicvaluechanged', this.onStateChanged);
279
+ if (this.capabilities.gyroscope) {
280
+ await writeGattCharacteristicValue(this.writeChrct, new Uint8Array([WRITE_ENABLE_ORIENTATION]).buffer).catch(() => { });
281
+ }
282
+ const firstStatePromise = new Promise((resolve) => {
283
+ this.resolveInitialState = resolve;
284
+ });
285
+ this.awaitingInitialState = true;
286
+ await writeGattCharacteristicValue(this.writeChrct, new Uint8Array([WRITE_STATE]).buffer);
287
+ this.pollBattery();
288
+ this.batteryInterval = setInterval(this.pollBattery, 60_000);
289
+ await Promise.race([
290
+ firstStatePromise,
291
+ new Promise((resolve) => setTimeout(resolve, INITIAL_STATE_TIMEOUT_MS))
292
+ ]);
293
+ this.awaitingInitialState = false;
294
+ this.resolveInitialState = undefined;
295
+ queueMicrotask(() => {
296
+ this.events$.next({
297
+ timestamp: now(),
298
+ type: "FACELETS",
299
+ facelets: this.prevCubie.toFaceCube()
300
+ });
301
+ });
302
+ }
303
+ async sendCommand(command) {
304
+ if (!this.writeChrct) {
305
+ return;
306
+ }
307
+ if (command.type === "REQUEST_BATTERY") {
308
+ this.forceNextBatteryEmission = true;
309
+ await writeGattCharacteristicValue(this.writeChrct, new Uint8Array([WRITE_BATTERY]).buffer);
310
+ }
311
+ else if (command.type === "REQUEST_FACELETS") {
312
+ const ts = now();
313
+ this.events$.next({
314
+ timestamp: ts,
315
+ type: "FACELETS",
316
+ facelets: this.prevCubie.toFaceCube()
317
+ });
318
+ await writeGattCharacteristicValue(this.writeChrct, new Uint8Array([WRITE_STATE]).buffer);
319
+ }
320
+ else if (command.type === "REQUEST_HARDWARE") {
321
+ this.emitHardwareEvent();
322
+ }
323
+ else if (command.type === "REQUEST_RESET") {
324
+ await writeGattCharacteristicValue(this.writeChrct, new Uint8Array([WRITE_RESET]).buffer);
325
+ this.curCubie = new CubieCube();
326
+ this.prevCubie = new CubieCube();
327
+ this.lastMoveMeta = null;
328
+ this.moveCntFree = 100;
329
+ this.lastBatteryLevel = null;
330
+ this.forceNextBatteryEmission = false;
331
+ this.events$.next({
332
+ timestamp: now(),
333
+ type: "FACELETS",
334
+ facelets: SOLVED_FACELET
335
+ });
336
+ }
337
+ }
338
+ async disconnect() {
339
+ if (this.readChrct) {
340
+ this.readChrct.removeEventListener('characteristicvaluechanged', this.onStateChanged);
341
+ await this.readChrct.stopNotifications().catch(() => { });
342
+ this.readChrct = null;
343
+ }
344
+ this.lastBatteryLevel = null;
345
+ this.forceNextBatteryEmission = false;
346
+ if (this.batteryInterval) {
347
+ clearInterval(this.batteryInterval);
348
+ this.batteryInterval = null;
349
+ }
350
+ this.writeChrct = null;
351
+ this.device.removeEventListener('gattserverdisconnected', this.onDisconnect);
352
+ this.events$.next({ timestamp: now(), type: "DISCONNECT" });
353
+ this.events$.complete();
354
+ if (this.device.gatt?.connected) {
355
+ this.device.gatt.disconnect();
356
+ }
357
+ }
358
+ }
359
+ const goCubeProtocol = {
360
+ nameFilters: [
361
+ { namePrefix: 'GoCube_' },
362
+ { namePrefix: 'GoCube' },
363
+ { namePrefix: 'Rubiks' }
364
+ ],
365
+ optionalServices: [SERVICE_UUID],
366
+ matchesDevice(device) {
367
+ const name = device.name || '';
368
+ return name.startsWith('GoCube') || name.startsWith('Rubiks');
369
+ },
370
+ gattAffinity(serviceUuids, _device) {
371
+ return serviceUuids.has(normalizeUuid(SERVICE_UUID)) ? 110 : 0;
372
+ },
373
+ async connect(device, _macProvider, _context) {
374
+ const raw = device.name ?? '';
375
+ const name = raw.startsWith('GoCube') ? 'GoCube' : 'Rubiks Connected';
376
+ const conn = new GoCubeConnection(device, name, goCubeDeviceSupportsGyro(raw));
377
+ await conn.init();
378
+ return conn;
379
+ }
380
+ };
381
+ registerProtocol(goCubeProtocol);
382
+ export { goCubeProtocol };
@@ -0,0 +1,3 @@
1
+ import { SmartCubeProtocol } from '../protocol.js';
2
+ declare const moyuMhcProtocol: SmartCubeProtocol;
3
+ export { moyuMhcProtocol };