@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.
- package/LICENSE +373 -0
- package/README.md +118 -0
- package/analyze/client.d.ts +18 -0
- package/analyze/client.js +42 -0
- package/analyze/index.d.ts +114 -0
- package/analyze/index.js +151 -0
- package/analyze/rotations.d.ts +3 -0
- package/analyze/rotations.js +15 -0
- package/analyze/roux.d.ts +67 -0
- package/analyze/roux.js +95 -0
- package/analyze/worker.d.ts +6 -0
- package/analyze/worker.js +31 -0
- package/analyze/zz.d.ts +47 -0
- package/analyze/zz.js +102 -0
- package/bld/index.d.ts +14 -0
- package/bld/index.js +14 -0
- package/bld/memo.d.ts +66 -0
- package/bld/memo.js +139 -0
- package/bld/scheme.d.ts +30 -0
- package/bld/scheme.js +35 -0
- package/bld/skin.d.ts +26 -0
- package/bld/skin.js +46 -0
- package/bld/tracker.d.ts +68 -0
- package/bld/tracker.js +145 -0
- package/bluetooth/clock.d.ts +22 -0
- package/bluetooth/clock.js +37 -0
- package/bluetooth/cubeSkins.d.ts +31 -0
- package/bluetooth/cubeSkins.js +32 -0
- package/bluetooth/gyro.d.ts +31 -0
- package/bluetooth/gyro.js +46 -0
- package/bluetooth/index.d.ts +5 -0
- package/bluetooth/index.js +5 -0
- package/bluetooth/session.d.ts +140 -0
- package/bluetooth/session.js +201 -0
- package/bluetooth/simulated.d.ts +52 -0
- package/bluetooth/simulated.js +80 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-definitions.d.ts +38 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-definitions.js +51 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-encrypter.d.ts +35 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-encrypter.js +70 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-protocol.d.ts +273 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-protocol.js +846 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen1.d.ts +38 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen1.js +340 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen234-packet-validate.d.ts +11 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen234-packet-validate.js +118 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-mac-salt.d.ts +2 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-mac-salt.js +15 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-cube.d.ts +18 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-cube.js +152 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-timer.d.ts +74 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-timer.js +161 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-write-queue.d.ts +7 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-write-queue.js +11 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gatt-characteristic-write.d.ts +2 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gatt-characteristic-write.js +36 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/index.d.ts +4 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/index.js +4 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/address-hints.d.ts +12 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/address-hints.js +122 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/build-picker-options.d.ts +11 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/build-picker-options.js +55 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/default-optional-services.d.ts +21 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/default-optional-services.js +47 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/device-name-scan-filters.d.ts +6 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/device-name-scan-filters.js +24 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/gatt-snapshot.d.ts +6 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/gatt-snapshot.js +57 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-candidates.d.ts +11 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-candidates.js +67 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-moyu32.d.ts +8 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-moyu32.js +98 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-qiyi.d.ts +8 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-qiyi.js +128 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/moyu32-session-crypto.d.ts +5 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/moyu32-session-crypto.js +60 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/normalize-uuid.d.ts +2 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/normalize-uuid.js +8 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/packet-sanity.d.ts +7 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/packet-sanity.js +142 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/profile-rank.d.ts +6 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/profile-rank.js +21 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/types.d.ts +32 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/types.js +1 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/ble-utils.d.ts +6 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/ble-utils.js +68 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/connect.d.ts +3 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/connect.js +178 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/cubie-cube.d.ts +16 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/cubie-cube.js +141 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/index.d.ts +11 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/index.js +10 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocol.d.ts +26 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocol.js +8 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gan.d.ts +3 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gan.js +265 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/giiker.d.ts +3 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/giiker.js +345 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gocube.d.ts +15 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gocube.js +382 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-mhc.d.ts +3 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-mhc.js +352 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-v1.d.ts +57 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-v1.js +243 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu32.d.ts +3 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu32.js +454 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/qiyi.d.ts +3 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/qiyi.js +424 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/types.d.ts +78 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/types.js +1 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/utils.d.ts +45 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/utils.js +150 -0
- package/cfop/caseData.d.ts +16 -0
- package/cfop/caseData.js +88 -0
- package/cfop/cases.d.ts +42 -0
- package/cfop/cases.js +116 -0
- package/cfop/f2l.d.ts +28 -0
- package/cfop/f2l.js +88 -0
- package/cfop/f2lData.d.ts +15 -0
- package/cfop/f2lData.js +49 -0
- package/cfop/index.d.ts +41 -0
- package/cfop/index.js +110 -0
- package/cfop/slots.d.ts +2 -0
- package/cfop/slots.js +1 -0
- package/core/arrows.d.ts +24 -0
- package/core/arrows.js +22 -0
- package/core/bytes.d.ts +27 -0
- package/core/bytes.js +145 -0
- package/core/centers.d.ts +32 -0
- package/core/centers.js +83 -0
- package/core/checks.d.ts +53 -0
- package/core/checks.js +85 -0
- package/core/cubies.d.ts +45 -0
- package/core/cubies.js +169 -0
- package/core/frames.d.ts +61 -0
- package/core/frames.js +126 -0
- package/core/geometry.d.ts +58 -0
- package/core/geometry.js +132 -0
- package/core/index.d.ts +19 -0
- package/core/index.js +19 -0
- package/core/masks.d.ts +32 -0
- package/core/masks.js +39 -0
- package/core/method.d.ts +35 -0
- package/core/method.js +30 -0
- package/core/moves.d.ts +48 -0
- package/core/moves.js +88 -0
- package/core/notation.d.ts +48 -0
- package/core/notation.js +226 -0
- package/core/physical.d.ts +53 -0
- package/core/physical.js +99 -0
- package/core/practice.d.ts +62 -0
- package/core/practice.js +87 -0
- package/core/sequence.d.ts +90 -0
- package/core/sequence.js +157 -0
- package/core/stages.d.ts +90 -0
- package/core/stages.js +21 -0
- package/core/state.d.ts +55 -0
- package/core/state.js +140 -0
- package/core/stateCodec.d.ts +12 -0
- package/core/stateCodec.js +51 -0
- package/core/stream.d.ts +45 -0
- package/core/stream.js +75 -0
- package/core/tracker.d.ts +62 -0
- package/core/tracker.js +121 -0
- package/element/alg.d.ts +45 -0
- package/element/alg.js +150 -0
- package/element/base.d.ts +2 -0
- package/element/base.js +3 -0
- package/element/bld.d.ts +82 -0
- package/element/bld.js +222 -0
- package/element/index.d.ts +8 -0
- package/element/index.js +21 -0
- package/element/model.d.ts +36 -0
- package/element/model.js +64 -0
- package/element/player.d.ts +196 -0
- package/element/player.js +680 -0
- package/element/practice.d.ts +75 -0
- package/element/practice.js +150 -0
- package/element/scramble.d.ts +50 -0
- package/element/scramble.js +107 -0
- package/element/sequence.d.ts +126 -0
- package/element/sequence.js +254 -0
- package/element/styles.d.ts +32 -0
- package/element/styles.js +196 -0
- package/image/cache.d.ts +21 -0
- package/image/cache.js +66 -0
- package/image/index.d.ts +2 -0
- package/image/index.js +2 -0
- package/image/png-node.d.ts +6 -0
- package/image/png-node.js +10 -0
- package/image/svg.d.ts +36 -0
- package/image/svg.js +215 -0
- package/lbl/index.d.ts +10 -0
- package/lbl/index.js +31 -0
- package/methods/index.d.ts +20 -0
- package/methods/index.js +33 -0
- package/package.json +133 -0
- package/petrus/index.d.ts +11 -0
- package/petrus/index.js +27 -0
- package/react/index.d.ts +176 -0
- package/react/index.js +228 -0
- package/render/arrows.d.ts +68 -0
- package/render/arrows.js +190 -0
- package/render/build/attachments.d.ts +35 -0
- package/render/build/attachments.js +94 -0
- package/render/build/models.d.ts +9 -0
- package/render/build/models.js +27 -0
- package/render/build/tiles.d.ts +48 -0
- package/render/build/tiles.js +194 -0
- package/render/gltf.d.ts +10 -0
- package/render/gltf.js +106 -0
- package/render/index.d.ts +12 -0
- package/render/index.js +12 -0
- package/render/layers.d.ts +21 -0
- package/render/layers.js +24 -0
- package/render/pictures.d.ts +46 -0
- package/render/pictures.js +80 -0
- package/render/pieceModels.d.ts +38 -0
- package/render/pieceModels.js +43 -0
- package/render/renderer.d.ts +165 -0
- package/render/renderer.js +647 -0
- package/render/tile.d.ts +114 -0
- package/render/tile.js +390 -0
- package/render/viewports.d.ts +25 -0
- package/render/viewports.js +26 -0
- package/roux/cmll.d.ts +35 -0
- package/roux/cmll.js +121 -0
- package/roux/cmllData.d.ts +12 -0
- package/roux/cmllData.js +49 -0
- package/roux/index.d.ts +52 -0
- package/roux/index.js +175 -0
- package/skin/attachments.d.ts +82 -0
- package/skin/attachments.js +110 -0
- package/skin/index.d.ts +3 -0
- package/skin/index.js +3 -0
- package/skin/shapes.d.ts +112 -0
- package/skin/shapes.js +147 -0
- package/skin/skin.d.ts +911 -0
- package/skin/skin.js +412 -0
- package/solve/client.d.ts +58 -0
- package/solve/client.js +56 -0
- package/solve/handle.d.ts +6 -0
- package/solve/handle.js +38 -0
- package/solve/index.d.ts +8 -0
- package/solve/index.js +7 -0
- package/solve/lse.d.ts +42 -0
- package/solve/lse.js +151 -0
- package/solve/pieces.d.ts +18 -0
- package/solve/pieces.js +24 -0
- package/solve/protocol.d.ts +70 -0
- package/solve/protocol.js +16 -0
- package/solve/scramble.d.ts +76 -0
- package/solve/scramble.js +187 -0
- package/solve/stage.d.ts +108 -0
- package/solve/stage.js +532 -0
- package/solve/twophase.d.ts +36 -0
- package/solve/twophase.js +229 -0
- package/solve/worker.d.ts +6 -0
- package/solve/worker.js +15 -0
- package/timeline/codec.d.ts +23 -0
- package/timeline/codec.js +84 -0
- package/timeline/index.d.ts +7 -0
- package/timeline/index.js +7 -0
- package/timeline/recording.d.ts +19 -0
- package/timeline/recording.js +20 -0
- package/timeline/replay.d.ts +68 -0
- package/timeline/replay.js +145 -0
- package/timeline/segments.d.ts +31 -0
- package/timeline/segments.js +39 -0
- package/timeline/share.d.ts +30 -0
- package/timeline/share.js +62 -0
- package/timeline/stats.d.ts +34 -0
- package/timeline/stats.js +56 -0
- package/timeline/timings.d.ts +30 -0
- package/timeline/timings.js +54 -0
- package/zz/index.d.ts +29 -0
- package/zz/index.js +68 -0
- package/zz/seeds.d.ts +9 -0
- package/zz/seeds.js +29 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gyroscope → renderer orientation. A smart cube reports its rotation as a
|
|
3
|
+
* quaternion in its own axes; the renderer (`CubeRenderer.setOrientation`)
|
|
4
|
+
* wants one in ours (x right, y up, z towards you) where identity = the cube
|
|
5
|
+
* as drawn. `calibrate` (or the first reading) says "the cube is held as
|
|
6
|
+
* shown right now".
|
|
7
|
+
*/
|
|
8
|
+
export const IDENTITY = { x: 0, y: 0, z: 0, w: 1 };
|
|
9
|
+
export function multiply(a, b) {
|
|
10
|
+
return {
|
|
11
|
+
w: a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z,
|
|
12
|
+
x: a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
|
|
13
|
+
y: a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
|
|
14
|
+
z: a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export const conjugate = (q) => ({ x: -q.x, y: -q.y, z: -q.z, w: q.w });
|
|
18
|
+
export function normalize(q) {
|
|
19
|
+
const l = Math.hypot(q.x, q.y, q.z, q.w) || 1;
|
|
20
|
+
return { x: q.x / l, y: q.y / l, z: q.z / l, w: q.w / l };
|
|
21
|
+
}
|
|
22
|
+
export const GAN_AXES = (q) => ({ x: q.x, y: q.z, z: -q.y, w: q.w });
|
|
23
|
+
export class GyroCalibrator {
|
|
24
|
+
axes;
|
|
25
|
+
basis = null;
|
|
26
|
+
last = null;
|
|
27
|
+
constructor(axes = GAN_AXES) {
|
|
28
|
+
this.axes = axes;
|
|
29
|
+
}
|
|
30
|
+
/** A raw reading → the orientation to show (identity at calibration). The first reading calibrates. */
|
|
31
|
+
orientation(raw) {
|
|
32
|
+
const q = normalize(this.axes(raw));
|
|
33
|
+
this.last = q;
|
|
34
|
+
this.basis ??= conjugate(q);
|
|
35
|
+
return normalize(multiply(this.basis, q));
|
|
36
|
+
}
|
|
37
|
+
/** "Held as shown now": the latest reading becomes identity. */
|
|
38
|
+
calibrate() {
|
|
39
|
+
if (this.last)
|
|
40
|
+
this.basis = conjugate(this.last);
|
|
41
|
+
}
|
|
42
|
+
reset() {
|
|
43
|
+
this.basis = null;
|
|
44
|
+
this.last = null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SmartCubeSession — a connected smart cube as cubecore sees it. Wraps
|
|
3
|
+
* smartcube-web-bluetooth (GAN, MoYu, QiYi, GoCube, Giiker…) and turns its
|
|
4
|
+
* events into cubecore values:
|
|
5
|
+
*
|
|
6
|
+
* move each face turn as a Move, with a trustworthy time (see clock.ts)
|
|
7
|
+
* and whether it arrived late; plus the state after it
|
|
8
|
+
* state the tracked cube state — also after a FACELETS report that
|
|
9
|
+
* disagrees with it (a missed move: the cube knows best)
|
|
10
|
+
* orientation gyro, calibrated, ready for CubeRenderer.setOrientation
|
|
11
|
+
* battery, hardware, disconnect
|
|
12
|
+
*
|
|
13
|
+
* Feed the moves to MoveCollapser (written log), OrientationTracker,
|
|
14
|
+
* MethodTracker or a timeline recording as needed.
|
|
15
|
+
*/
|
|
16
|
+
import { type Move, type State } from "../core/index.js";
|
|
17
|
+
import type { ConnectSmartCubeOptions, SmartCubeCapabilities, SmartCubeCommand, SmartCubeEvent } from "./vendor/smartcube-web-bluetooth/index.js";
|
|
18
|
+
import type { Skin } from "../skin/index.js";
|
|
19
|
+
import { type AxisMap, type Quat } from "./gyro.js";
|
|
20
|
+
/** What the session needs from a connection — smartcube-web-bluetooth's, or a SimulatedCube. */
|
|
21
|
+
export interface CubeConnection {
|
|
22
|
+
readonly deviceName: string;
|
|
23
|
+
readonly deviceMAC: string;
|
|
24
|
+
readonly protocol: {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
};
|
|
28
|
+
readonly capabilities: SmartCubeCapabilities;
|
|
29
|
+
events$: {
|
|
30
|
+
subscribe(next: (e: SmartCubeEvent) => void): {
|
|
31
|
+
unsubscribe(): void;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
sendCommand(command: SmartCubeCommand): Promise<void>;
|
|
35
|
+
disconnect(): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
export interface CubeMoveEvent {
|
|
38
|
+
move: Move;
|
|
39
|
+
/** When it happened, on the page's clock (performance.now()). */
|
|
40
|
+
time: number;
|
|
41
|
+
/** The cube's own timestamp, if it sends one. */
|
|
42
|
+
cubeTime: number | null;
|
|
43
|
+
/** Arrived much later than it happened (a backgrounded tab, a resent packet). */
|
|
44
|
+
late: boolean;
|
|
45
|
+
/** The state after this move. */
|
|
46
|
+
state: State;
|
|
47
|
+
}
|
|
48
|
+
export interface HardwareInfo {
|
|
49
|
+
name?: string;
|
|
50
|
+
softwareVersion?: string;
|
|
51
|
+
hardwareVersion?: string;
|
|
52
|
+
gyroSupported?: boolean;
|
|
53
|
+
}
|
|
54
|
+
interface Events {
|
|
55
|
+
move: CubeMoveEvent;
|
|
56
|
+
state: {
|
|
57
|
+
state: State;
|
|
58
|
+
reason: "move" | "facelets" | "reset";
|
|
59
|
+
};
|
|
60
|
+
orientation: Quat;
|
|
61
|
+
battery: number;
|
|
62
|
+
hardware: HardwareInfo;
|
|
63
|
+
disconnect: undefined;
|
|
64
|
+
/** The cube's own state that counts as solved changed (see `base`) — keep it to pass back next time. */
|
|
65
|
+
base: State | null;
|
|
66
|
+
}
|
|
67
|
+
export interface SessionOptions {
|
|
68
|
+
/** A move is `late` when it arrives this much after it happened. Default 1000 ms. */
|
|
69
|
+
lateMs?: number;
|
|
70
|
+
/** Gyro axes of this cube (default: GAN). */
|
|
71
|
+
axes?: AxisMap;
|
|
72
|
+
/**
|
|
73
|
+
* For a cube that can't reset its own state (QiYi…): the state of it that
|
|
74
|
+
* counts as solved, kept from an earlier connection (the `base` event) —
|
|
75
|
+
* or a function picking it by the cube (name / MAC).
|
|
76
|
+
*/
|
|
77
|
+
base?: State | null | ((info: {
|
|
78
|
+
name: string;
|
|
79
|
+
mac: string | null;
|
|
80
|
+
}) => State | null);
|
|
81
|
+
}
|
|
82
|
+
export declare class SmartCubeSession {
|
|
83
|
+
private readonly connection;
|
|
84
|
+
private listeners;
|
|
85
|
+
private _state;
|
|
86
|
+
private _battery;
|
|
87
|
+
private _hardware;
|
|
88
|
+
private readonly clock;
|
|
89
|
+
private readonly gyro;
|
|
90
|
+
private readonly subscription;
|
|
91
|
+
private readonly lateMs;
|
|
92
|
+
private connected;
|
|
93
|
+
/** Ask the browser for a cube and connect (needs a user gesture; Chrome / Edge / Bluefy). */
|
|
94
|
+
static connect(options?: ConnectSmartCubeOptions, session?: SessionOptions): Promise<SmartCubeSession>;
|
|
95
|
+
constructor(connection: CubeConnection, options?: SessionOptions);
|
|
96
|
+
get info(): {
|
|
97
|
+
name: string;
|
|
98
|
+
mac: string;
|
|
99
|
+
protocol: {
|
|
100
|
+
id: string;
|
|
101
|
+
name: string;
|
|
102
|
+
};
|
|
103
|
+
capabilities: SmartCubeCapabilities;
|
|
104
|
+
};
|
|
105
|
+
get state(): State;
|
|
106
|
+
get battery(): number | null;
|
|
107
|
+
get hardware(): HardwareInfo;
|
|
108
|
+
/** A skin that suits this cube (see cubeSkins.ts; refines once the hardware name arrives). */
|
|
109
|
+
get suggestedSkin(): Skin;
|
|
110
|
+
get isConnected(): boolean;
|
|
111
|
+
on<K extends keyof Events>(type: K, listener: (e: Events[K]) => void): () => void;
|
|
112
|
+
/** "Held as shown now" for the gyro. */
|
|
113
|
+
calibrate(): void;
|
|
114
|
+
/**
|
|
115
|
+
* Tell the session (and the cube, if it can) that the cube is solved now.
|
|
116
|
+
* Cubes that can't reset their own state (QiYi, …) keep reporting it: the
|
|
117
|
+
* next facelets report becomes the reference ("solved" as marked) and
|
|
118
|
+
* later reports are read relative to it — so they don't undo the mark.
|
|
119
|
+
*/
|
|
120
|
+
markSolved(): void;
|
|
121
|
+
/**
|
|
122
|
+
* The cube's own state that counts as solved (a cube that can't reset,
|
|
123
|
+
* after "mark solved"), or null: its reports are its state as they are.
|
|
124
|
+
*/
|
|
125
|
+
get base(): State | null;
|
|
126
|
+
/** Set the reference from an earlier connection (see `base`); the state is read again. */
|
|
127
|
+
setBase(base: State | null): void;
|
|
128
|
+
requestState(): void;
|
|
129
|
+
requestBattery(): void;
|
|
130
|
+
disconnect(): Promise<void>;
|
|
131
|
+
private send;
|
|
132
|
+
private emit;
|
|
133
|
+
/** The cube's own state that counts as solved (after "mark solved" on a cube that didn't reset), or null. */
|
|
134
|
+
private _base;
|
|
135
|
+
/** "Mark solved" was pressed: the next facelets report sets `base`. */
|
|
136
|
+
private rebase;
|
|
137
|
+
private setState;
|
|
138
|
+
private onEvent;
|
|
139
|
+
}
|
|
140
|
+
export {};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SmartCubeSession — a connected smart cube as cubecore sees it. Wraps
|
|
3
|
+
* smartcube-web-bluetooth (GAN, MoYu, QiYi, GoCube, Giiker…) and turns its
|
|
4
|
+
* events into cubecore values:
|
|
5
|
+
*
|
|
6
|
+
* move each face turn as a Move, with a trustworthy time (see clock.ts)
|
|
7
|
+
* and whether it arrived late; plus the state after it
|
|
8
|
+
* state the tracked cube state — also after a FACELETS report that
|
|
9
|
+
* disagrees with it (a missed move: the cube knows best)
|
|
10
|
+
* orientation gyro, calibrated, ready for CubeRenderer.setOrientation
|
|
11
|
+
* battery, hardware, disconnect
|
|
12
|
+
*
|
|
13
|
+
* Feed the moves to MoveCollapser (written log), OrientationTracker,
|
|
14
|
+
* MethodTracker or a timeline recording as needed.
|
|
15
|
+
*/
|
|
16
|
+
import { applyMove, parseAlg, relativeState, solvedState, stateFromFacelets, statesEqual } from "../core/index.js";
|
|
17
|
+
import { ClockSync } from "./clock.js";
|
|
18
|
+
import { skinForCube } from "./cubeSkins.js";
|
|
19
|
+
import { GAN_AXES, GyroCalibrator } from "./gyro.js";
|
|
20
|
+
export class SmartCubeSession {
|
|
21
|
+
connection;
|
|
22
|
+
listeners = new Map();
|
|
23
|
+
_state = solvedState();
|
|
24
|
+
_battery = null;
|
|
25
|
+
_hardware = {};
|
|
26
|
+
clock = new ClockSync();
|
|
27
|
+
gyro;
|
|
28
|
+
subscription;
|
|
29
|
+
lateMs;
|
|
30
|
+
connected = true;
|
|
31
|
+
/** Ask the browser for a cube and connect (needs a user gesture; Chrome / Edge / Bluefy). */
|
|
32
|
+
static async connect(options, session) {
|
|
33
|
+
const { connectSmartCube } = await import("./vendor/smartcube-web-bluetooth/index.js");
|
|
34
|
+
return new SmartCubeSession(await connectSmartCube(options), session);
|
|
35
|
+
}
|
|
36
|
+
constructor(connection, options = {}) {
|
|
37
|
+
this.connection = connection;
|
|
38
|
+
this.lateMs = options.lateMs ?? 1000;
|
|
39
|
+
this.gyro = new GyroCalibrator(options.axes ?? GAN_AXES);
|
|
40
|
+
const base = typeof options.base === "function" ? options.base({ name: connection.deviceName, mac: connection.deviceMAC ?? null }) : options.base;
|
|
41
|
+
this._base = base ?? null;
|
|
42
|
+
this.subscription = connection.events$.subscribe((e) => this.onEvent(e));
|
|
43
|
+
if (connection.capabilities.facelets)
|
|
44
|
+
this.send({ type: "REQUEST_FACELETS" });
|
|
45
|
+
if (connection.capabilities.battery)
|
|
46
|
+
this.send({ type: "REQUEST_BATTERY" });
|
|
47
|
+
if (connection.capabilities.hardware)
|
|
48
|
+
this.send({ type: "REQUEST_HARDWARE" });
|
|
49
|
+
}
|
|
50
|
+
get info() {
|
|
51
|
+
const c = this.connection;
|
|
52
|
+
return { name: c.deviceName, mac: c.deviceMAC, protocol: c.protocol, capabilities: c.capabilities };
|
|
53
|
+
}
|
|
54
|
+
get state() {
|
|
55
|
+
return this._state;
|
|
56
|
+
}
|
|
57
|
+
get battery() {
|
|
58
|
+
return this._battery;
|
|
59
|
+
}
|
|
60
|
+
get hardware() {
|
|
61
|
+
return this._hardware;
|
|
62
|
+
}
|
|
63
|
+
/** A skin that suits this cube (see cubeSkins.ts; refines once the hardware name arrives). */
|
|
64
|
+
get suggestedSkin() {
|
|
65
|
+
return skinForCube({ protocol: this.connection.protocol, name: this.connection.deviceName, hardwareName: this._hardware.name });
|
|
66
|
+
}
|
|
67
|
+
get isConnected() {
|
|
68
|
+
return this.connected;
|
|
69
|
+
}
|
|
70
|
+
on(type, listener) {
|
|
71
|
+
let set = this.listeners.get(type);
|
|
72
|
+
if (!set)
|
|
73
|
+
this.listeners.set(type, (set = new Set()));
|
|
74
|
+
set.add(listener);
|
|
75
|
+
return () => set.delete(listener);
|
|
76
|
+
}
|
|
77
|
+
/** "Held as shown now" for the gyro. */
|
|
78
|
+
calibrate() {
|
|
79
|
+
this.gyro.calibrate();
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Tell the session (and the cube, if it can) that the cube is solved now.
|
|
83
|
+
* Cubes that can't reset their own state (QiYi, …) keep reporting it: the
|
|
84
|
+
* next facelets report becomes the reference ("solved" as marked) and
|
|
85
|
+
* later reports are read relative to it — so they don't undo the mark.
|
|
86
|
+
*/
|
|
87
|
+
markSolved() {
|
|
88
|
+
if (this.connection.capabilities.reset)
|
|
89
|
+
this.send({ type: "REQUEST_RESET" });
|
|
90
|
+
this.setState(solvedState(), "reset");
|
|
91
|
+
if (this.connection.capabilities.facelets) {
|
|
92
|
+
this.rebase = true;
|
|
93
|
+
this.send({ type: "REQUEST_FACELETS" });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The cube's own state that counts as solved (a cube that can't reset,
|
|
98
|
+
* after "mark solved"), or null: its reports are its state as they are.
|
|
99
|
+
*/
|
|
100
|
+
get base() {
|
|
101
|
+
return this._base;
|
|
102
|
+
}
|
|
103
|
+
/** Set the reference from an earlier connection (see `base`); the state is read again. */
|
|
104
|
+
setBase(base) {
|
|
105
|
+
this._base = base;
|
|
106
|
+
this.rebase = false;
|
|
107
|
+
if (this.connection.capabilities.facelets)
|
|
108
|
+
this.send({ type: "REQUEST_FACELETS" });
|
|
109
|
+
}
|
|
110
|
+
requestState() {
|
|
111
|
+
this.send({ type: "REQUEST_FACELETS" });
|
|
112
|
+
}
|
|
113
|
+
requestBattery() {
|
|
114
|
+
this.send({ type: "REQUEST_BATTERY" });
|
|
115
|
+
}
|
|
116
|
+
async disconnect() {
|
|
117
|
+
this.subscription.unsubscribe();
|
|
118
|
+
await this.connection.disconnect();
|
|
119
|
+
this.onEvent({ type: "DISCONNECT", timestamp: performance.now() });
|
|
120
|
+
}
|
|
121
|
+
send(command) {
|
|
122
|
+
this.connection.sendCommand(command).catch(() => undefined);
|
|
123
|
+
}
|
|
124
|
+
emit(type, e) {
|
|
125
|
+
for (const l of this.listeners.get(type) ?? [])
|
|
126
|
+
l(e);
|
|
127
|
+
}
|
|
128
|
+
/** The cube's own state that counts as solved (after "mark solved" on a cube that didn't reset), or null. */
|
|
129
|
+
_base = null;
|
|
130
|
+
/** "Mark solved" was pressed: the next facelets report sets `base`. */
|
|
131
|
+
rebase = false;
|
|
132
|
+
setState(state, reason) {
|
|
133
|
+
this._state = state;
|
|
134
|
+
this.emit("state", { state, reason });
|
|
135
|
+
}
|
|
136
|
+
onEvent(e) {
|
|
137
|
+
switch (e.type) {
|
|
138
|
+
case "MOVE": {
|
|
139
|
+
const [move] = parseAlg(e.move.replace(/’/g, "'"));
|
|
140
|
+
if (!move)
|
|
141
|
+
return;
|
|
142
|
+
const arrived = e.localTimestamp ?? e.timestamp;
|
|
143
|
+
let time = arrived;
|
|
144
|
+
if (e.cubeTimestamp !== null) {
|
|
145
|
+
this.clock.observe(e.cubeTimestamp, e.localTimestamp);
|
|
146
|
+
time = this.clock.toLocal(e.cubeTimestamp) ?? arrived;
|
|
147
|
+
}
|
|
148
|
+
const state = applyMove(this._state, move);
|
|
149
|
+
this._state = state;
|
|
150
|
+
this.emit("move", { move, time, cubeTime: e.cubeTimestamp, late: e.timestamp - time > this.lateMs, state });
|
|
151
|
+
this.emit("state", { state, reason: "move" });
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
case "FACELETS": {
|
|
155
|
+
const reported = stateFromFacelets(e.facelets);
|
|
156
|
+
if (!reported)
|
|
157
|
+
return;
|
|
158
|
+
if (this.rebase) {
|
|
159
|
+
// First report after "mark solved": the cube's own state then (moves since the mark taken off) is the new "solved".
|
|
160
|
+
const base = baseOf(reported, this._state);
|
|
161
|
+
// A cube that did reset reports solved: no reference to keep.
|
|
162
|
+
this._base = statesEqual(base, solvedState()) ? null : base;
|
|
163
|
+
this.rebase = false;
|
|
164
|
+
this.emit("base", this._base);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const s = this._base ? relativeState(reported, this._base) : reported;
|
|
168
|
+
if (!statesEqual(s, this._state))
|
|
169
|
+
this.setState(s, "facelets");
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
case "GYRO":
|
|
173
|
+
this.emit("orientation", this.gyro.orientation(e.quaternion));
|
|
174
|
+
break;
|
|
175
|
+
case "BATTERY":
|
|
176
|
+
this._battery = e.batteryLevel;
|
|
177
|
+
this.emit("battery", e.batteryLevel);
|
|
178
|
+
break;
|
|
179
|
+
case "HARDWARE":
|
|
180
|
+
this._hardware = { name: e.hardwareName, softwareVersion: e.softwareVersion, hardwareVersion: e.hardwareVersion, gyroSupported: e.gyroSupported };
|
|
181
|
+
this.emit("hardware", this._hardware);
|
|
182
|
+
break;
|
|
183
|
+
case "DISCONNECT":
|
|
184
|
+
if (!this.connected)
|
|
185
|
+
return;
|
|
186
|
+
this.connected = false;
|
|
187
|
+
this.subscription.unsubscribe();
|
|
188
|
+
this.emit("disconnect", undefined);
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* The cube's own state at the mark: `reported` is it with the moves since
|
|
195
|
+
* (`sinceMark`, a state from solved) done — so base[x] = reported[inverse(sinceMark)[x]].
|
|
196
|
+
*/
|
|
197
|
+
function baseOf(reported, sinceMark) {
|
|
198
|
+
const inverse = new Uint8Array(sinceMark.length);
|
|
199
|
+
sinceMark.forEach((v, i) => (inverse[v] = i));
|
|
200
|
+
return Uint8Array.from(inverse, (_, x) => reported[inverse[x]]);
|
|
201
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A pretend smart cube with the same events as a real one — for tests,
|
|
3
|
+
* demos and building an app without a cube at hand. `new
|
|
4
|
+
* SmartCubeSession(new SimulatedCube())` behaves like a connected cube.
|
|
5
|
+
*/
|
|
6
|
+
import { type Move, type State } from "../core/index.js";
|
|
7
|
+
import type { SmartCubeCommand, SmartCubeEvent } from "./vendor/smartcube-web-bluetooth/index.js";
|
|
8
|
+
import type { CubeConnection } from "./session.js";
|
|
9
|
+
import type { Quat } from "./gyro.js";
|
|
10
|
+
export declare class SimulatedCube implements CubeConnection {
|
|
11
|
+
readonly deviceName = "Simulated cube";
|
|
12
|
+
readonly deviceMAC = "00:00:00:00:00:00";
|
|
13
|
+
readonly protocol: {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
};
|
|
17
|
+
readonly capabilities: {
|
|
18
|
+
gyroscope: boolean;
|
|
19
|
+
battery: boolean;
|
|
20
|
+
facelets: boolean;
|
|
21
|
+
hardware: boolean;
|
|
22
|
+
reset: boolean;
|
|
23
|
+
};
|
|
24
|
+
/** Also report the whole state after every move (as QiYi cubes do). */
|
|
25
|
+
private readonly faceletsOnMove;
|
|
26
|
+
private subscribers;
|
|
27
|
+
private state;
|
|
28
|
+
private cubeClock;
|
|
29
|
+
/** Simulated Bluetooth latency added to local arrival times (ms). */
|
|
30
|
+
latency: number;
|
|
31
|
+
/** `canReset: false` + `faceletsOnMove: true` behave like a QiYi cube. */
|
|
32
|
+
constructor(options?: {
|
|
33
|
+
canReset?: boolean;
|
|
34
|
+
faceletsOnMove?: boolean;
|
|
35
|
+
});
|
|
36
|
+
readonly events$: {
|
|
37
|
+
subscribe: (next: (e: SmartCubeEvent) => void) => {
|
|
38
|
+
unsubscribe: () => boolean;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
/** Turn the cube. `delayMs` = how late the notification is (e.g. a backgrounded tab); `dropLocalTime` = a resent move. */
|
|
42
|
+
turn(moves: string | Move, options?: {
|
|
43
|
+
delayMs?: number;
|
|
44
|
+
dropLocalTime?: boolean;
|
|
45
|
+
}): void;
|
|
46
|
+
tilt(quaternion: Quat): void;
|
|
47
|
+
/** Change the cube behind the session's back (e.g. a missed move); it only notices on a facelets report. */
|
|
48
|
+
setStateSilently(state: State): void;
|
|
49
|
+
sendCommand(command: SmartCubeCommand): Promise<void>;
|
|
50
|
+
disconnect(): Promise<void>;
|
|
51
|
+
private emit;
|
|
52
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A pretend smart cube with the same events as a real one — for tests,
|
|
3
|
+
* demos and building an app without a cube at hand. `new
|
|
4
|
+
* SmartCubeSession(new SimulatedCube())` behaves like a connected cube.
|
|
5
|
+
*/
|
|
6
|
+
import { applyMove, faceletsOf, formatMove, parseAlg, solvedState } from "../core/index.js";
|
|
7
|
+
export class SimulatedCube {
|
|
8
|
+
deviceName = "Simulated cube";
|
|
9
|
+
deviceMAC = "00:00:00:00:00:00";
|
|
10
|
+
protocol = { id: "simulated", name: "Simulated" };
|
|
11
|
+
capabilities;
|
|
12
|
+
/** Also report the whole state after every move (as QiYi cubes do). */
|
|
13
|
+
faceletsOnMove;
|
|
14
|
+
subscribers = new Set();
|
|
15
|
+
state = solvedState();
|
|
16
|
+
cubeClock = 0;
|
|
17
|
+
/** Simulated Bluetooth latency added to local arrival times (ms). */
|
|
18
|
+
latency = 20;
|
|
19
|
+
/** `canReset: false` + `faceletsOnMove: true` behave like a QiYi cube. */
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
this.capabilities = { gyroscope: true, battery: true, facelets: true, hardware: true, reset: options.canReset ?? true };
|
|
22
|
+
this.faceletsOnMove = options.faceletsOnMove ?? false;
|
|
23
|
+
}
|
|
24
|
+
events$ = {
|
|
25
|
+
subscribe: (next) => {
|
|
26
|
+
this.subscribers.add(next);
|
|
27
|
+
return { unsubscribe: () => this.subscribers.delete(next) };
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
/** Turn the cube. `delayMs` = how late the notification is (e.g. a backgrounded tab); `dropLocalTime` = a resent move. */
|
|
31
|
+
turn(moves, options = {}) {
|
|
32
|
+
for (const m of typeof moves === "string" ? parseAlg(moves) : [moves]) {
|
|
33
|
+
if (!"URFDLB".includes(m.family))
|
|
34
|
+
throw new Error("A smart cube only turns faces");
|
|
35
|
+
const quarters = m.amount === 2 ? [{ ...m, amount: 1 }, { ...m, amount: 1 }] : [m];
|
|
36
|
+
for (const q of quarters) {
|
|
37
|
+
this.state = applyMove(this.state, q);
|
|
38
|
+
const happened = performance.now();
|
|
39
|
+
this.cubeClock = Math.round(happened) + 100_000; // any epoch: only differences matter
|
|
40
|
+
const arrived = happened + this.latency + (options.delayMs ?? 0);
|
|
41
|
+
this.emit({
|
|
42
|
+
type: "MOVE",
|
|
43
|
+
timestamp: arrived,
|
|
44
|
+
face: "URFDLB".indexOf(q.family),
|
|
45
|
+
direction: q.amount === -1 ? 1 : 0,
|
|
46
|
+
move: formatMove(q),
|
|
47
|
+
localTimestamp: options.dropLocalTime ? null : arrived,
|
|
48
|
+
cubeTimestamp: this.cubeClock,
|
|
49
|
+
});
|
|
50
|
+
if (this.faceletsOnMove)
|
|
51
|
+
this.emit({ type: "FACELETS", timestamp: arrived, facelets: faceletsOf(this.state) });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
tilt(quaternion) {
|
|
56
|
+
this.emit({ type: "GYRO", timestamp: performance.now(), quaternion });
|
|
57
|
+
}
|
|
58
|
+
/** Change the cube behind the session's back (e.g. a missed move); it only notices on a facelets report. */
|
|
59
|
+
setStateSilently(state) {
|
|
60
|
+
this.state = new Uint8Array(state);
|
|
61
|
+
}
|
|
62
|
+
async sendCommand(command) {
|
|
63
|
+
const timestamp = performance.now();
|
|
64
|
+
if (command.type === "REQUEST_FACELETS")
|
|
65
|
+
this.emit({ type: "FACELETS", timestamp, facelets: faceletsOf(this.state) });
|
|
66
|
+
if (command.type === "REQUEST_BATTERY")
|
|
67
|
+
this.emit({ type: "BATTERY", timestamp, batteryLevel: 87 });
|
|
68
|
+
if (command.type === "REQUEST_HARDWARE")
|
|
69
|
+
this.emit({ type: "HARDWARE", timestamp, hardwareName: "Simulated", gyroSupported: true });
|
|
70
|
+
if (command.type === "REQUEST_RESET" && this.capabilities.reset)
|
|
71
|
+
this.state = solvedState();
|
|
72
|
+
}
|
|
73
|
+
async disconnect() {
|
|
74
|
+
this.emit({ type: "DISCONNECT", timestamp: performance.now() });
|
|
75
|
+
}
|
|
76
|
+
emit(e) {
|
|
77
|
+
for (const s of [...this.subscribers])
|
|
78
|
+
s(e);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** LZ-compressed key blobs for GAN gen1 `deriveKey`. */
|
|
2
|
+
export declare const GAN_GEN1_COMPRESSED_KEYS: readonly ["NoRgnAHANATADDWJYwMxQOxiiEcfYgSK6Hpr4TYCs0IG1OEAbDszALpA", "NoNg7ANATFIQnARmogLBRUCs0oAYN8U5J45EQBmFADg0oJAOSlUQF0g", "NoRgNATGBs1gLABgQTjCeBWSUDsYBmKbCeMADjNnXxHIoIF0g", "NoRg7ANAzBCsAMEAsioxBEIAc0Cc0ATJkgSIYhXIjhMQGxgC6QA", "NoVgNAjAHGBMYDYCcdJgCwTFBkYVgAY9JpJYUsYBmAXSA", "NoRgNAbAHGAsAMkwgMyzClH0LFcArHnAJzIqIBMGWEAukA"];
|
|
3
|
+
/** GAN gen1 primary GATT service (356i “API v1”). */
|
|
4
|
+
export declare const GAN_GEN1_PRIMARY_SERVICE = "0000fff0-0000-1000-8000-00805f9b34fb";
|
|
5
|
+
/** Standard Device Information service (gen1 key derivation). */
|
|
6
|
+
export declare const GAN_GEN1_DEVICE_INFO_SERVICE = "0000180a-0000-1000-8000-00805f9b34fb";
|
|
7
|
+
export declare const GAN_GEN1_CHR_FIRMWARE = "00002a28-0000-1000-8000-00805f9b34fb";
|
|
8
|
+
export declare const GAN_GEN1_CHR_HARDWARE = "00002a23-0000-1000-8000-00805f9b34fb";
|
|
9
|
+
export declare const GAN_GEN1_CHR_STATE = "0000fff5-0000-1000-8000-00805f9b34fb";
|
|
10
|
+
export declare const GAN_GEN1_CHR_MOVES = "0000fff6-0000-1000-8000-00805f9b34fb";
|
|
11
|
+
export declare const GAN_GEN1_CHR_GYRO_NOTIFY = "0000fff4-0000-1000-8000-00805f9b34fb";
|
|
12
|
+
export declare const GAN_GEN1_CHR_BATTERY = "0000fff7-0000-1000-8000-00805f9b34fb";
|
|
13
|
+
export declare const GAN_GEN1_CHR_FACELETS = "0000fff2-0000-1000-8000-00805f9b34fb";
|
|
14
|
+
/** GAN Gen2 protocol BLE service */
|
|
15
|
+
export declare const GAN_GEN2_SERVICE = "6e400001-b5a3-f393-e0a9-e50e24dc4179";
|
|
16
|
+
/** GAN Gen2 protocol BLE command characteristic */
|
|
17
|
+
export declare const GAN_GEN2_COMMAND_CHARACTERISTIC = "28be4a4a-cd67-11e9-a32f-2a2ae2dbcce4";
|
|
18
|
+
/** GAN Gen2 protocol BLE state characteristic */
|
|
19
|
+
export declare const GAN_GEN2_STATE_CHARACTERISTIC = "28be4cb6-cd67-11e9-a32f-2a2ae2dbcce4";
|
|
20
|
+
/** GAN Gen3 protocol BLE service */
|
|
21
|
+
export declare const GAN_GEN3_SERVICE = "8653000a-43e6-47b7-9cb0-5fc21d4ae340";
|
|
22
|
+
/** GAN Gen3 protocol BLE command characteristic */
|
|
23
|
+
export declare const GAN_GEN3_COMMAND_CHARACTERISTIC = "8653000c-43e6-47b7-9cb0-5fc21d4ae340";
|
|
24
|
+
/** GAN Gen3 protocol BLE state characteristic */
|
|
25
|
+
export declare const GAN_GEN3_STATE_CHARACTERISTIC = "8653000b-43e6-47b7-9cb0-5fc21d4ae340";
|
|
26
|
+
/** GAN Gen4 protocol BLE service */
|
|
27
|
+
export declare const GAN_GEN4_SERVICE = "00000010-0000-fff7-fff6-fff5fff4fff0";
|
|
28
|
+
/** GAN Gen4 protocol BLE command characteristic */
|
|
29
|
+
export declare const GAN_GEN4_COMMAND_CHARACTERISTIC = "0000fff5-0000-1000-8000-00805f9b34fb";
|
|
30
|
+
/** GAN Gen4 protocol BLE state characteristic */
|
|
31
|
+
export declare const GAN_GEN4_STATE_CHARACTERISTIC = "0000fff6-0000-1000-8000-00805f9b34fb";
|
|
32
|
+
/** List of Company Identifier Codes, fill with all values [0x0001, 0xFF01] possible for GAN cubes */
|
|
33
|
+
export declare const GAN_CIC_LIST: number[];
|
|
34
|
+
/** List of encryption keys */
|
|
35
|
+
export declare const GAN_ENCRYPTION_KEYS: {
|
|
36
|
+
key: number[];
|
|
37
|
+
iv: number[];
|
|
38
|
+
}[];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** LZ-compressed key blobs for GAN gen1 `deriveKey`. */
|
|
2
|
+
export const GAN_GEN1_COMPRESSED_KEYS = [
|
|
3
|
+
"NoRgnAHANATADDWJYwMxQOxiiEcfYgSK6Hpr4TYCs0IG1OEAbDszALpA",
|
|
4
|
+
"NoNg7ANATFIQnARmogLBRUCs0oAYN8U5J45EQBmFADg0oJAOSlUQF0g",
|
|
5
|
+
"NoRgNATGBs1gLABgQTjCeBWSUDsYBmKbCeMADjNnXxHIoIF0g",
|
|
6
|
+
"NoRg7ANAzBCsAMEAsioxBEIAc0Cc0ATJkgSIYhXIjhMQGxgC6QA",
|
|
7
|
+
"NoVgNAjAHGBMYDYCcdJgCwTFBkYVgAY9JpJYUsYBmAXSA",
|
|
8
|
+
"NoRgNAbAHGAsAMkwgMyzClH0LFcArHnAJzIqIBMGWEAukA",
|
|
9
|
+
];
|
|
10
|
+
/** GAN gen1 primary GATT service (356i “API v1”). */
|
|
11
|
+
export const GAN_GEN1_PRIMARY_SERVICE = "0000fff0-0000-1000-8000-00805f9b34fb";
|
|
12
|
+
/** Standard Device Information service (gen1 key derivation). */
|
|
13
|
+
export const GAN_GEN1_DEVICE_INFO_SERVICE = "0000180a-0000-1000-8000-00805f9b34fb";
|
|
14
|
+
export const GAN_GEN1_CHR_FIRMWARE = "00002a28-0000-1000-8000-00805f9b34fb";
|
|
15
|
+
export const GAN_GEN1_CHR_HARDWARE = "00002a23-0000-1000-8000-00805f9b34fb";
|
|
16
|
+
export const GAN_GEN1_CHR_STATE = "0000fff5-0000-1000-8000-00805f9b34fb";
|
|
17
|
+
export const GAN_GEN1_CHR_MOVES = "0000fff6-0000-1000-8000-00805f9b34fb";
|
|
18
|
+
export const GAN_GEN1_CHR_GYRO_NOTIFY = "0000fff4-0000-1000-8000-00805f9b34fb";
|
|
19
|
+
export const GAN_GEN1_CHR_BATTERY = "0000fff7-0000-1000-8000-00805f9b34fb";
|
|
20
|
+
export const GAN_GEN1_CHR_FACELETS = "0000fff2-0000-1000-8000-00805f9b34fb";
|
|
21
|
+
/** GAN Gen2 protocol BLE service */
|
|
22
|
+
export const GAN_GEN2_SERVICE = "6e400001-b5a3-f393-e0a9-e50e24dc4179";
|
|
23
|
+
/** GAN Gen2 protocol BLE command characteristic */
|
|
24
|
+
export const GAN_GEN2_COMMAND_CHARACTERISTIC = "28be4a4a-cd67-11e9-a32f-2a2ae2dbcce4";
|
|
25
|
+
/** GAN Gen2 protocol BLE state characteristic */
|
|
26
|
+
export const GAN_GEN2_STATE_CHARACTERISTIC = "28be4cb6-cd67-11e9-a32f-2a2ae2dbcce4";
|
|
27
|
+
/** GAN Gen3 protocol BLE service */
|
|
28
|
+
export const GAN_GEN3_SERVICE = "8653000a-43e6-47b7-9cb0-5fc21d4ae340";
|
|
29
|
+
/** GAN Gen3 protocol BLE command characteristic */
|
|
30
|
+
export const GAN_GEN3_COMMAND_CHARACTERISTIC = "8653000c-43e6-47b7-9cb0-5fc21d4ae340";
|
|
31
|
+
/** GAN Gen3 protocol BLE state characteristic */
|
|
32
|
+
export const GAN_GEN3_STATE_CHARACTERISTIC = "8653000b-43e6-47b7-9cb0-5fc21d4ae340";
|
|
33
|
+
/** GAN Gen4 protocol BLE service */
|
|
34
|
+
export const GAN_GEN4_SERVICE = "00000010-0000-fff7-fff6-fff5fff4fff0";
|
|
35
|
+
/** GAN Gen4 protocol BLE command characteristic */
|
|
36
|
+
export const GAN_GEN4_COMMAND_CHARACTERISTIC = "0000fff5-0000-1000-8000-00805f9b34fb";
|
|
37
|
+
/** GAN Gen4 protocol BLE state characteristic */
|
|
38
|
+
export const GAN_GEN4_STATE_CHARACTERISTIC = "0000fff6-0000-1000-8000-00805f9b34fb";
|
|
39
|
+
/** List of Company Identifier Codes, fill with all values [0x0001, 0xFF01] possible for GAN cubes */
|
|
40
|
+
export const GAN_CIC_LIST = Array(256).fill(undefined).map((_v, i) => (i << 8) | 0x01);
|
|
41
|
+
/** List of encryption keys */
|
|
42
|
+
export const GAN_ENCRYPTION_KEYS = [
|
|
43
|
+
{
|
|
44
|
+
key: [0x01, 0x02, 0x42, 0x28, 0x31, 0x91, 0x16, 0x07, 0x20, 0x05, 0x18, 0x54, 0x42, 0x11, 0x12, 0x53],
|
|
45
|
+
iv: [0x11, 0x03, 0x32, 0x28, 0x21, 0x01, 0x76, 0x27, 0x20, 0x95, 0x78, 0x14, 0x32, 0x12, 0x02, 0x43]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
key: [0x05, 0x12, 0x02, 0x45, 0x02, 0x01, 0x29, 0x56, 0x12, 0x78, 0x12, 0x76, 0x81, 0x01, 0x08, 0x03],
|
|
49
|
+
iv: [0x01, 0x44, 0x28, 0x06, 0x86, 0x21, 0x22, 0x28, 0x51, 0x05, 0x08, 0x31, 0x82, 0x02, 0x21, 0x06]
|
|
50
|
+
}
|
|
51
|
+
];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common cube encrypter interface
|
|
3
|
+
*/
|
|
4
|
+
interface GanCubeEncrypter {
|
|
5
|
+
/** Encrypt binary message buffer represented as Uint8Array */
|
|
6
|
+
encrypt(data: Uint8Array): Uint8Array;
|
|
7
|
+
/** Decrypt binary message buffer represented as Uint8Array */
|
|
8
|
+
decrypt(data: Uint8Array): Uint8Array;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Implementation for encryption scheme used in the GAN Gen2 Smart Cubes
|
|
12
|
+
*/
|
|
13
|
+
declare class GanGen2CubeEncrypter implements GanCubeEncrypter {
|
|
14
|
+
private _key;
|
|
15
|
+
private _iv;
|
|
16
|
+
constructor(key: Uint8Array, iv: Uint8Array, salt: Uint8Array);
|
|
17
|
+
/** Encrypt 16-byte buffer chunk starting at offset using AES-128-CBC */
|
|
18
|
+
private encryptChunk;
|
|
19
|
+
/** Decrypt 16-byte buffer chunk starting at offset using AES-128-CBC */
|
|
20
|
+
private decryptChunk;
|
|
21
|
+
encrypt(data: Uint8Array): Uint8Array;
|
|
22
|
+
decrypt(data: Uint8Array): Uint8Array;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Implementation for encryption scheme used in the GAN Gen3 cubes
|
|
26
|
+
*/
|
|
27
|
+
declare class GanGen3CubeEncrypter extends GanGen2CubeEncrypter {
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Implementation for encryption scheme used in the GAN Gen3 cubes
|
|
31
|
+
*/
|
|
32
|
+
declare class GanGen4CubeEncrypter extends GanGen2CubeEncrypter {
|
|
33
|
+
}
|
|
34
|
+
export type { GanCubeEncrypter };
|
|
35
|
+
export { GanGen2CubeEncrypter, GanGen3CubeEncrypter, GanGen4CubeEncrypter };
|