@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,265 @@
|
|
|
1
|
+
import { Subject } from 'rxjs';
|
|
2
|
+
import { normalizeUuid } from '../attachment/normalize-uuid.js';
|
|
3
|
+
import { getCachedMacForDevice, macFromGanManufacturerData, waitForManufacturerData } from '../attachment/address-hints.js';
|
|
4
|
+
import { registerProtocol } from '../protocol.js';
|
|
5
|
+
import * as def from '../../gan-cube-definitions.js';
|
|
6
|
+
import { GanGen2CubeEncrypter, GanGen3CubeEncrypter, GanGen4CubeEncrypter } from '../../gan-cube-encrypter.js';
|
|
7
|
+
import { GanGen1CubeConnection } from '../../gan-gen1.js';
|
|
8
|
+
import { macStringToSaltOrThrow } from '../../gan-mac-salt.js';
|
|
9
|
+
import { isValidGanGen2Packet, isValidGanGen3Packet, isValidGanGen4Packet, } from '../../gan-gen234-packet-validate.js';
|
|
10
|
+
import { GanCubeClassicConnection, GanGen2ProtocolDriver, GanGen3ProtocolDriver, GanGen4ProtocolDriver } from '../../gan-cube-protocol.js';
|
|
11
|
+
const DEFAULT_GAN_CAPABILITIES = {
|
|
12
|
+
gyroscope: true,
|
|
13
|
+
battery: true,
|
|
14
|
+
facelets: true,
|
|
15
|
+
hardware: true,
|
|
16
|
+
reset: true,
|
|
17
|
+
};
|
|
18
|
+
const GAN_GEN1_CAPABILITIES = {
|
|
19
|
+
gyroscope: true,
|
|
20
|
+
battery: true,
|
|
21
|
+
facelets: true,
|
|
22
|
+
hardware: false,
|
|
23
|
+
reset: false,
|
|
24
|
+
};
|
|
25
|
+
const GAN_GEN1_PROTOCOL = { id: 'gan-gen1', name: 'GAN Gen1' };
|
|
26
|
+
const GAN_GEN2_PROTOCOL = { id: 'gan-gen2', name: 'GAN Gen2' };
|
|
27
|
+
const GAN_GEN3_PROTOCOL = { id: 'gan-gen3', name: 'GAN Gen3' };
|
|
28
|
+
const GAN_GEN4_PROTOCOL = { id: 'gan-gen4', name: 'GAN Gen4' };
|
|
29
|
+
function ganEventToSmartEvent(event) {
|
|
30
|
+
switch (event.type) {
|
|
31
|
+
case "MOVE":
|
|
32
|
+
return {
|
|
33
|
+
timestamp: event.timestamp,
|
|
34
|
+
type: "MOVE",
|
|
35
|
+
face: event.face,
|
|
36
|
+
direction: event.direction,
|
|
37
|
+
move: event.move,
|
|
38
|
+
localTimestamp: event.localTimestamp,
|
|
39
|
+
cubeTimestamp: event.cubeTimestamp
|
|
40
|
+
};
|
|
41
|
+
case "FACELETS":
|
|
42
|
+
return {
|
|
43
|
+
timestamp: event.timestamp,
|
|
44
|
+
type: "FACELETS",
|
|
45
|
+
facelets: event.facelets
|
|
46
|
+
};
|
|
47
|
+
case "GYRO":
|
|
48
|
+
return {
|
|
49
|
+
timestamp: event.timestamp,
|
|
50
|
+
type: "GYRO",
|
|
51
|
+
quaternion: event.quaternion,
|
|
52
|
+
velocity: event.velocity
|
|
53
|
+
};
|
|
54
|
+
case "BATTERY":
|
|
55
|
+
return {
|
|
56
|
+
timestamp: event.timestamp,
|
|
57
|
+
type: "BATTERY",
|
|
58
|
+
batteryLevel: event.batteryLevel
|
|
59
|
+
};
|
|
60
|
+
case "HARDWARE":
|
|
61
|
+
return {
|
|
62
|
+
timestamp: event.timestamp,
|
|
63
|
+
type: "HARDWARE",
|
|
64
|
+
hardwareName: event.hardwareName,
|
|
65
|
+
softwareVersion: event.softwareVersion,
|
|
66
|
+
hardwareVersion: event.hardwareVersion,
|
|
67
|
+
productDate: event.productDate,
|
|
68
|
+
gyroSupported: event.gyroSupported
|
|
69
|
+
};
|
|
70
|
+
case "DISCONNECT":
|
|
71
|
+
return {
|
|
72
|
+
timestamp: event.timestamp,
|
|
73
|
+
type: "DISCONNECT"
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function hasGanGen1Profile(serviceUuids) {
|
|
78
|
+
const primary = normalizeUuid(def.GAN_GEN1_PRIMARY_SERVICE);
|
|
79
|
+
const deviceInfo = normalizeUuid(def.GAN_GEN1_DEVICE_INFO_SERVICE);
|
|
80
|
+
return serviceUuids.has(primary) && serviceUuids.has(deviceInfo);
|
|
81
|
+
}
|
|
82
|
+
class GanSmartCubeConnection {
|
|
83
|
+
ganConn;
|
|
84
|
+
deviceMac;
|
|
85
|
+
lastBatteryLevel = null;
|
|
86
|
+
forceNextBatteryEmission = false;
|
|
87
|
+
events$;
|
|
88
|
+
protocol;
|
|
89
|
+
capabilities;
|
|
90
|
+
constructor(ganConn, mac, protocol, capabilities) {
|
|
91
|
+
this.ganConn = ganConn;
|
|
92
|
+
this.deviceMac = mac;
|
|
93
|
+
this.protocol = protocol;
|
|
94
|
+
const base = capabilities ? { ...capabilities } : { ...DEFAULT_GAN_CAPABILITIES };
|
|
95
|
+
if (!capabilities && ganConn.deviceName?.startsWith('AiCube')) {
|
|
96
|
+
base.gyroscope = false;
|
|
97
|
+
}
|
|
98
|
+
this.capabilities = base;
|
|
99
|
+
this.events$ = new Subject();
|
|
100
|
+
ganConn.events$.subscribe({
|
|
101
|
+
next: (event) => {
|
|
102
|
+
if (event.type === 'HARDWARE' &&
|
|
103
|
+
this.protocol.id === 'gan-gen2' &&
|
|
104
|
+
typeof event.gyroSupported === 'boolean') {
|
|
105
|
+
this.capabilities.gyroscope = event.gyroSupported;
|
|
106
|
+
}
|
|
107
|
+
if (event.type === 'BATTERY') {
|
|
108
|
+
const batteryLevel = Math.min(100, Math.max(0, Math.round(event.batteryLevel)));
|
|
109
|
+
const forceEmission = this.forceNextBatteryEmission;
|
|
110
|
+
this.forceNextBatteryEmission = false;
|
|
111
|
+
if (!forceEmission && this.lastBatteryLevel === batteryLevel) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
this.lastBatteryLevel = batteryLevel;
|
|
115
|
+
this.events$.next({
|
|
116
|
+
timestamp: event.timestamp,
|
|
117
|
+
type: 'BATTERY',
|
|
118
|
+
batteryLevel,
|
|
119
|
+
});
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
this.events$.next(ganEventToSmartEvent(event));
|
|
123
|
+
},
|
|
124
|
+
complete: () => this.events$.complete(),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
get deviceName() {
|
|
128
|
+
return this.ganConn.deviceName;
|
|
129
|
+
}
|
|
130
|
+
get deviceMAC() {
|
|
131
|
+
return this.deviceMac;
|
|
132
|
+
}
|
|
133
|
+
async sendCommand(command) {
|
|
134
|
+
if (command.type === 'REQUEST_BATTERY') {
|
|
135
|
+
this.forceNextBatteryEmission = true;
|
|
136
|
+
}
|
|
137
|
+
return this.ganConn.sendCubeCommand(command);
|
|
138
|
+
}
|
|
139
|
+
async disconnect() {
|
|
140
|
+
this.forceNextBatteryEmission = false;
|
|
141
|
+
return this.ganConn.disconnect();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function connectGanDevice(device, macProvider, context) {
|
|
145
|
+
const bleDevice = device;
|
|
146
|
+
const gatt = device.gatt;
|
|
147
|
+
if (!gatt.connected) {
|
|
148
|
+
await gatt.connect();
|
|
149
|
+
}
|
|
150
|
+
const services = await gatt.getPrimaryServices();
|
|
151
|
+
const serviceUuidSet = new Set(services.map((s) => normalizeUuid(s.uuid)));
|
|
152
|
+
if (hasGanGen1Profile(serviceUuidSet)) {
|
|
153
|
+
const gen1Conn = await GanGen1CubeConnection.create(device);
|
|
154
|
+
return new GanSmartCubeConnection(gen1Conn, '', GAN_GEN1_PROTOCOL, GAN_GEN1_CAPABILITIES);
|
|
155
|
+
}
|
|
156
|
+
let mac = null;
|
|
157
|
+
if (context?.advertisementManufacturerData) {
|
|
158
|
+
mac = macFromGanManufacturerData(context.advertisementManufacturerData);
|
|
159
|
+
}
|
|
160
|
+
mac = mac || getCachedMacForDevice(device);
|
|
161
|
+
if (!mac && macProvider) {
|
|
162
|
+
const r = await macProvider(device, false);
|
|
163
|
+
if (r) {
|
|
164
|
+
mac = r;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (!mac) {
|
|
168
|
+
const mf = await waitForManufacturerData(device, 5000);
|
|
169
|
+
if (mf) {
|
|
170
|
+
mac = macFromGanManufacturerData(mf);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (!mac && macProvider) {
|
|
174
|
+
const r = await macProvider(device, true);
|
|
175
|
+
if (r) {
|
|
176
|
+
mac = r;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (!mac) {
|
|
180
|
+
throw new Error('Unable to determine cube MAC address, connection is not possible!');
|
|
181
|
+
}
|
|
182
|
+
bleDevice.mac = mac;
|
|
183
|
+
const salt = macStringToSaltOrThrow(mac);
|
|
184
|
+
const g2 = normalizeUuid(def.GAN_GEN2_SERVICE);
|
|
185
|
+
const g3 = normalizeUuid(def.GAN_GEN3_SERVICE);
|
|
186
|
+
const g4 = normalizeUuid(def.GAN_GEN4_SERVICE);
|
|
187
|
+
let pick = null;
|
|
188
|
+
if (serviceUuidSet.has(g2))
|
|
189
|
+
pick = 'g2';
|
|
190
|
+
else if (serviceUuidSet.has(g3))
|
|
191
|
+
pick = 'g3';
|
|
192
|
+
else if (serviceUuidSet.has(g4))
|
|
193
|
+
pick = 'g4';
|
|
194
|
+
let ganConn = null;
|
|
195
|
+
if (pick === 'g2') {
|
|
196
|
+
const service = await gatt.getPrimaryService(def.GAN_GEN2_SERVICE);
|
|
197
|
+
const commandCharacteristic = await service.getCharacteristic(def.GAN_GEN2_COMMAND_CHARACTERISTIC);
|
|
198
|
+
const stateCharacteristic = await service.getCharacteristic(def.GAN_GEN2_STATE_CHARACTERISTIC);
|
|
199
|
+
const key = device.name?.startsWith('AiCube') ? def.GAN_ENCRYPTION_KEYS[1] : def.GAN_ENCRYPTION_KEYS[0];
|
|
200
|
+
const encrypter = new GanGen2CubeEncrypter(new Uint8Array(key.key), new Uint8Array(key.iv), salt);
|
|
201
|
+
const driver = new GanGen2ProtocolDriver();
|
|
202
|
+
ganConn = await GanCubeClassicConnection.create(bleDevice, commandCharacteristic, stateCharacteristic, encrypter, driver, { validateDecrypted: isValidGanGen2Packet });
|
|
203
|
+
}
|
|
204
|
+
else if (pick === 'g3') {
|
|
205
|
+
const service = await gatt.getPrimaryService(def.GAN_GEN3_SERVICE);
|
|
206
|
+
const commandCharacteristic = await service.getCharacteristic(def.GAN_GEN3_COMMAND_CHARACTERISTIC);
|
|
207
|
+
const stateCharacteristic = await service.getCharacteristic(def.GAN_GEN3_STATE_CHARACTERISTIC);
|
|
208
|
+
const key = def.GAN_ENCRYPTION_KEYS[0];
|
|
209
|
+
const encrypter = new GanGen3CubeEncrypter(new Uint8Array(key.key), new Uint8Array(key.iv), salt);
|
|
210
|
+
const driver = new GanGen3ProtocolDriver();
|
|
211
|
+
ganConn = await GanCubeClassicConnection.create(bleDevice, commandCharacteristic, stateCharacteristic, encrypter, driver, { validateDecrypted: isValidGanGen3Packet });
|
|
212
|
+
}
|
|
213
|
+
else if (pick === 'g4') {
|
|
214
|
+
const service = await gatt.getPrimaryService(def.GAN_GEN4_SERVICE);
|
|
215
|
+
const commandCharacteristic = await service.getCharacteristic(def.GAN_GEN4_COMMAND_CHARACTERISTIC);
|
|
216
|
+
const stateCharacteristic = await service.getCharacteristic(def.GAN_GEN4_STATE_CHARACTERISTIC);
|
|
217
|
+
const key = def.GAN_ENCRYPTION_KEYS[0];
|
|
218
|
+
const encrypter = new GanGen4CubeEncrypter(new Uint8Array(key.key), new Uint8Array(key.iv), salt);
|
|
219
|
+
const driver = new GanGen4ProtocolDriver();
|
|
220
|
+
ganConn = await GanCubeClassicConnection.create(bleDevice, commandCharacteristic, stateCharacteristic, encrypter, driver, { validateDecrypted: isValidGanGen4Packet });
|
|
221
|
+
}
|
|
222
|
+
if (!ganConn) {
|
|
223
|
+
throw new Error("Can't find target BLE services - wrong or unsupported cube device model");
|
|
224
|
+
}
|
|
225
|
+
return new GanSmartCubeConnection(ganConn, mac, pick === 'g2' ? GAN_GEN2_PROTOCOL : pick === 'g3' ? GAN_GEN3_PROTOCOL : GAN_GEN4_PROTOCOL);
|
|
226
|
+
}
|
|
227
|
+
const ganProtocol = {
|
|
228
|
+
nameFilters: [{ namePrefix: 'GAN' }, { namePrefix: 'MG' }, { namePrefix: 'AiCube' }],
|
|
229
|
+
optionalServices: [
|
|
230
|
+
def.GAN_GEN1_PRIMARY_SERVICE,
|
|
231
|
+
def.GAN_GEN1_DEVICE_INFO_SERVICE,
|
|
232
|
+
def.GAN_GEN2_SERVICE,
|
|
233
|
+
def.GAN_GEN3_SERVICE,
|
|
234
|
+
def.GAN_GEN4_SERVICE,
|
|
235
|
+
],
|
|
236
|
+
optionalManufacturerData: def.GAN_CIC_LIST,
|
|
237
|
+
matchesDevice(device) {
|
|
238
|
+
const name = device.name || '';
|
|
239
|
+
return name.startsWith('GAN') || name.startsWith('MG') || name.startsWith('AiCube');
|
|
240
|
+
},
|
|
241
|
+
gattAffinity(serviceUuids, _device) {
|
|
242
|
+
const g2 = normalizeUuid(def.GAN_GEN2_SERVICE);
|
|
243
|
+
const g3 = normalizeUuid(def.GAN_GEN3_SERVICE);
|
|
244
|
+
const g4 = normalizeUuid(def.GAN_GEN4_SERVICE);
|
|
245
|
+
const g1Primary = normalizeUuid(def.GAN_GEN1_PRIMARY_SERVICE);
|
|
246
|
+
const deviceInfo = normalizeUuid(def.GAN_GEN1_DEVICE_INFO_SERVICE);
|
|
247
|
+
const bonus = serviceUuids.has(deviceInfo) ? 5 : 0;
|
|
248
|
+
if (serviceUuids.has(g1Primary) && serviceUuids.has(deviceInfo)) {
|
|
249
|
+
return 125 + bonus;
|
|
250
|
+
}
|
|
251
|
+
if (serviceUuids.has(g4)) {
|
|
252
|
+
return 120 + bonus;
|
|
253
|
+
}
|
|
254
|
+
if (serviceUuids.has(g3)) {
|
|
255
|
+
return 120 + bonus;
|
|
256
|
+
}
|
|
257
|
+
if (serviceUuids.has(g2)) {
|
|
258
|
+
return 120 + bonus;
|
|
259
|
+
}
|
|
260
|
+
return 0;
|
|
261
|
+
},
|
|
262
|
+
connect: connectGanDevice
|
|
263
|
+
};
|
|
264
|
+
registerProtocol(ganProtocol);
|
|
265
|
+
export { ganProtocol };
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { Subject } from 'rxjs';
|
|
2
|
+
import { normalizeUuid } from '../attachment/normalize-uuid.js';
|
|
3
|
+
import { registerProtocol } from '../protocol.js';
|
|
4
|
+
import { CubieCube } from '../cubie-cube.js';
|
|
5
|
+
import { now, findCharacteristic } from '../ble-utils.js';
|
|
6
|
+
import { writeGattCharacteristicValue } from '../../gatt-characteristic-write.js';
|
|
7
|
+
const UUID_SUFFIX = '-0000-1000-8000-00805f9b34fb';
|
|
8
|
+
const SERVICE_UUID_DATA = '0000aadb' + UUID_SUFFIX;
|
|
9
|
+
const CHRCT_UUID_DATA = '0000aadc' + UUID_SUFFIX;
|
|
10
|
+
const SERVICE_UUID_RW = '0000aaaa' + UUID_SUFFIX;
|
|
11
|
+
const CHRCT_UUID_READ = '0000aaab' + UUID_SUFFIX;
|
|
12
|
+
const CHRCT_UUID_WRITE = '0000aaac' + UUID_SUFFIX;
|
|
13
|
+
const GIIKER_CFACELET = [
|
|
14
|
+
[26, 15, 29], [20, 8, 9], [18, 38, 6], [24, 27, 44],
|
|
15
|
+
[51, 35, 17], [45, 11, 2], [47, 0, 36], [53, 42, 33]
|
|
16
|
+
];
|
|
17
|
+
const GIIKER_EFACELET = [
|
|
18
|
+
[25, 28], [23, 12], [19, 7], [21, 41],
|
|
19
|
+
[32, 16], [5, 10], [3, 37], [30, 43],
|
|
20
|
+
[52, 34], [48, 14], [46, 1], [50, 39]
|
|
21
|
+
];
|
|
22
|
+
const DECRYPT_KEY = [176, 81, 104, 224, 86, 137, 237, 119, 38, 26, 193, 161, 210, 126, 150, 81, 93, 13, 236, 249, 89, 235, 88, 24, 113, 81, 214, 131, 130, 199, 2, 169, 39, 165, 171, 41];
|
|
23
|
+
const CO_MASK = [-1, 1, -1, 1, 1, -1, 1, -1];
|
|
24
|
+
function giikerHexPayload(value) {
|
|
25
|
+
const raw = [];
|
|
26
|
+
for (let i = 0; i < 20; i++) {
|
|
27
|
+
raw.push(value.getUint8(i));
|
|
28
|
+
}
|
|
29
|
+
let logicalBytes = raw.length;
|
|
30
|
+
if (raw[18] === 0xa7) {
|
|
31
|
+
const k1 = (raw[19] >> 4) & 0xf;
|
|
32
|
+
const k2 = raw[19] & 0xf;
|
|
33
|
+
for (let i = 0; i < 18; i++) {
|
|
34
|
+
raw[i] = (raw[i] + DECRYPT_KEY[i + k1] + DECRYPT_KEY[i + k2]) & 0xFF;
|
|
35
|
+
}
|
|
36
|
+
logicalBytes = 18;
|
|
37
|
+
}
|
|
38
|
+
const valhex = [];
|
|
39
|
+
for (let i = 0; i < logicalBytes; i++) {
|
|
40
|
+
valhex.push((raw[i] >> 4) & 0xf);
|
|
41
|
+
valhex.push(raw[i] & 0xf);
|
|
42
|
+
}
|
|
43
|
+
return { valhex, logicalBytes };
|
|
44
|
+
}
|
|
45
|
+
function giikerMoveString(faceNibble, dirNibble) {
|
|
46
|
+
const face = ["?", "B", "D", "L", "U", "R", "F"][faceNibble];
|
|
47
|
+
if (!face || face === "?")
|
|
48
|
+
return null;
|
|
49
|
+
// Observed mappings: 1/2 => "", 3 => "2", 4 => "'", and some firmwares use 9 for half-turn.
|
|
50
|
+
const dirKey = dirNibble === 9 ? 3 : dirNibble;
|
|
51
|
+
const suffix = ["", "", "2", "'"][dirKey] ?? "";
|
|
52
|
+
return `${face}${suffix}`;
|
|
53
|
+
}
|
|
54
|
+
function parseState(value) {
|
|
55
|
+
const { valhex } = giikerHexPayload(value);
|
|
56
|
+
const eo = [];
|
|
57
|
+
for (let i = 0; i < 3; i++) {
|
|
58
|
+
for (let mask = 8; mask !== 0; mask >>= 1) {
|
|
59
|
+
eo.push((valhex[i + 28] & mask) ? 1 : 0);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const cc = new CubieCube();
|
|
63
|
+
for (let i = 0; i < 8; i++) {
|
|
64
|
+
cc.ca[i] = (valhex[i] - 1) | (((3 + valhex[i + 8] * CO_MASK[i]) % 3) << 3);
|
|
65
|
+
}
|
|
66
|
+
for (let i = 0; i < 12; i++) {
|
|
67
|
+
cc.ea[i] = ((valhex[i + 16] - 1) << 1) | eo[i];
|
|
68
|
+
}
|
|
69
|
+
const facelet = cc.toFaceCube(GIIKER_CFACELET, GIIKER_EFACELET);
|
|
70
|
+
const prevMoves = [];
|
|
71
|
+
// Byte 16 is the current move (face/dir nibbles). Encrypted packets must ignore bytes 18–19.
|
|
72
|
+
const faceNibble = valhex[32];
|
|
73
|
+
const dirNibble = valhex[33];
|
|
74
|
+
if (faceNibble !== undefined && dirNibble !== undefined) {
|
|
75
|
+
const mv = giikerMoveString(faceNibble, dirNibble);
|
|
76
|
+
if (mv)
|
|
77
|
+
prevMoves.push(mv);
|
|
78
|
+
}
|
|
79
|
+
return { facelet, prevMoves };
|
|
80
|
+
}
|
|
81
|
+
const GIIKER_PROTOCOL = { id: 'giiker', name: 'Giiker' };
|
|
82
|
+
class GiikerConnection {
|
|
83
|
+
deviceName;
|
|
84
|
+
deviceMAC;
|
|
85
|
+
protocol = GIIKER_PROTOCOL;
|
|
86
|
+
capabilities = {
|
|
87
|
+
gyroscope: false,
|
|
88
|
+
battery: true,
|
|
89
|
+
facelets: true,
|
|
90
|
+
hardware: true,
|
|
91
|
+
reset: true
|
|
92
|
+
};
|
|
93
|
+
events$;
|
|
94
|
+
device;
|
|
95
|
+
gatt = null;
|
|
96
|
+
dataChrct = null;
|
|
97
|
+
lastFacelet = '';
|
|
98
|
+
isReady = false;
|
|
99
|
+
pendingValues = [];
|
|
100
|
+
rwReadChrct = null;
|
|
101
|
+
rwWriteChrct = null;
|
|
102
|
+
batteryInterval = null;
|
|
103
|
+
onBatteryChanged = null;
|
|
104
|
+
lastBatteryLevel = null;
|
|
105
|
+
forceNextBatteryEmission = false;
|
|
106
|
+
constructor(device, name) {
|
|
107
|
+
this.device = device;
|
|
108
|
+
this.deviceName = name;
|
|
109
|
+
this.deviceMAC = '';
|
|
110
|
+
this.events$ = new Subject();
|
|
111
|
+
}
|
|
112
|
+
onStateChanged = (event) => {
|
|
113
|
+
const value = event.target.value;
|
|
114
|
+
if (!value)
|
|
115
|
+
return;
|
|
116
|
+
if (!this.isReady) {
|
|
117
|
+
// Copy the view, because the underlying buffer can be reused by the platform.
|
|
118
|
+
const b = value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength);
|
|
119
|
+
this.pendingValues.push(new DataView(b));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const timestamp = now();
|
|
123
|
+
const { facelet, prevMoves } = parseState(value);
|
|
124
|
+
if (this.lastFacelet && this.lastFacelet !== facelet && prevMoves.length > 0) {
|
|
125
|
+
const moveStr = prevMoves[0].trim();
|
|
126
|
+
const face = "URFDLB".indexOf(moveStr[0]);
|
|
127
|
+
const direction = moveStr.includes("2") ? 2 : moveStr.includes("'") ? 1 : 0;
|
|
128
|
+
this.events$.next({
|
|
129
|
+
timestamp,
|
|
130
|
+
type: "MOVE",
|
|
131
|
+
face,
|
|
132
|
+
direction,
|
|
133
|
+
move: moveStr,
|
|
134
|
+
localTimestamp: timestamp,
|
|
135
|
+
cubeTimestamp: null
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
this.lastFacelet = facelet;
|
|
139
|
+
this.events$.next({
|
|
140
|
+
timestamp,
|
|
141
|
+
type: "FACELETS",
|
|
142
|
+
facelets: facelet
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
emitHardwareEvent() {
|
|
146
|
+
this.events$.next({
|
|
147
|
+
timestamp: now(),
|
|
148
|
+
type: "HARDWARE",
|
|
149
|
+
hardwareName: this.deviceName,
|
|
150
|
+
gyroSupported: false
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
emitBatteryLevel(rawLevel, timestamp = now()) {
|
|
154
|
+
if (!Number.isFinite(rawLevel)) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const batteryLevel = Math.min(100, Math.max(0, Math.round(rawLevel)));
|
|
158
|
+
const forceEmission = this.forceNextBatteryEmission;
|
|
159
|
+
this.forceNextBatteryEmission = false;
|
|
160
|
+
if (!forceEmission && this.lastBatteryLevel === batteryLevel) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
this.lastBatteryLevel = batteryLevel;
|
|
164
|
+
this.events$.next({
|
|
165
|
+
timestamp,
|
|
166
|
+
type: 'BATTERY',
|
|
167
|
+
batteryLevel,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
onDisconnect = () => {
|
|
171
|
+
this.device.removeEventListener('gattserverdisconnected', this.onDisconnect);
|
|
172
|
+
this.isReady = false;
|
|
173
|
+
this.pendingValues = [];
|
|
174
|
+
this.lastBatteryLevel = null;
|
|
175
|
+
this.forceNextBatteryEmission = false;
|
|
176
|
+
if (this.batteryInterval) {
|
|
177
|
+
clearInterval(this.batteryInterval);
|
|
178
|
+
this.batteryInterval = null;
|
|
179
|
+
}
|
|
180
|
+
if (this.rwReadChrct && this.onBatteryChanged) {
|
|
181
|
+
this.rwReadChrct.removeEventListener('characteristicvaluechanged', this.onBatteryChanged);
|
|
182
|
+
}
|
|
183
|
+
this.onBatteryChanged = null;
|
|
184
|
+
this.events$.next({ timestamp: now(), type: "DISCONNECT" });
|
|
185
|
+
this.events$.complete();
|
|
186
|
+
};
|
|
187
|
+
async init() {
|
|
188
|
+
this.device.addEventListener('gattserverdisconnected', this.onDisconnect);
|
|
189
|
+
this.gatt = await this.device.gatt.connect();
|
|
190
|
+
const dataService = await this.gatt.getPrimaryService(SERVICE_UUID_DATA);
|
|
191
|
+
this.dataChrct = await dataService.getCharacteristic(CHRCT_UUID_DATA);
|
|
192
|
+
// Attach listener before notifications to reduce missed packets.
|
|
193
|
+
this.dataChrct.addEventListener('characteristicvaluechanged', this.onStateChanged);
|
|
194
|
+
await this.dataChrct.startNotifications();
|
|
195
|
+
const initialValue = await this.dataChrct.readValue();
|
|
196
|
+
const { facelet } = parseState(initialValue);
|
|
197
|
+
this.lastFacelet = facelet;
|
|
198
|
+
const timestamp = now();
|
|
199
|
+
this.events$.next({
|
|
200
|
+
timestamp,
|
|
201
|
+
type: "FACELETS",
|
|
202
|
+
facelets: facelet
|
|
203
|
+
});
|
|
204
|
+
// Setup periodic battery polling if the control service is available.
|
|
205
|
+
try {
|
|
206
|
+
const rwService = await this.gatt.getPrimaryService(SERVICE_UUID_RW);
|
|
207
|
+
const chrcts = await rwService.getCharacteristics();
|
|
208
|
+
this.rwReadChrct = findCharacteristic(chrcts, CHRCT_UUID_READ);
|
|
209
|
+
this.rwWriteChrct = findCharacteristic(chrcts, CHRCT_UUID_WRITE);
|
|
210
|
+
if (this.rwReadChrct && this.rwWriteChrct) {
|
|
211
|
+
this.onBatteryChanged = (evt) => {
|
|
212
|
+
const val = evt.target.value;
|
|
213
|
+
if (!val)
|
|
214
|
+
return;
|
|
215
|
+
this.emitBatteryLevel(val.getUint8(1));
|
|
216
|
+
};
|
|
217
|
+
this.rwReadChrct.addEventListener('characteristicvaluechanged', this.onBatteryChanged);
|
|
218
|
+
await this.rwReadChrct.startNotifications();
|
|
219
|
+
const tick = () => {
|
|
220
|
+
if (!this.rwWriteChrct)
|
|
221
|
+
return;
|
|
222
|
+
writeGattCharacteristicValue(this.rwWriteChrct, new Uint8Array([0xb5]).buffer).catch(() => { });
|
|
223
|
+
};
|
|
224
|
+
tick();
|
|
225
|
+
this.batteryInterval = setInterval(tick, 60_000);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
// Battery service may not be available
|
|
230
|
+
}
|
|
231
|
+
// Release any queued notifications that arrived during init.
|
|
232
|
+
this.isReady = true;
|
|
233
|
+
const queued = this.pendingValues;
|
|
234
|
+
this.pendingValues = [];
|
|
235
|
+
for (const dv of queued) {
|
|
236
|
+
// Reuse the same logic path.
|
|
237
|
+
const ts2 = now();
|
|
238
|
+
const { facelet: f2, prevMoves: m2 } = parseState(dv);
|
|
239
|
+
if (this.lastFacelet && this.lastFacelet !== f2 && m2.length > 0) {
|
|
240
|
+
const moveStr = m2[0].trim();
|
|
241
|
+
const face = "URFDLB".indexOf(moveStr[0]);
|
|
242
|
+
const direction = moveStr.includes("2") ? 2 : moveStr.includes("'") ? 1 : 0;
|
|
243
|
+
this.events$.next({
|
|
244
|
+
timestamp: ts2,
|
|
245
|
+
type: "MOVE",
|
|
246
|
+
face,
|
|
247
|
+
direction,
|
|
248
|
+
move: moveStr,
|
|
249
|
+
localTimestamp: ts2,
|
|
250
|
+
cubeTimestamp: null
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
this.lastFacelet = f2;
|
|
254
|
+
this.events$.next({
|
|
255
|
+
timestamp: ts2,
|
|
256
|
+
type: "FACELETS",
|
|
257
|
+
facelets: f2
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
async sendCommand(command) {
|
|
262
|
+
if (command.type === "REQUEST_BATTERY") {
|
|
263
|
+
// Periodic battery polling is set up in init when available.
|
|
264
|
+
if (this.rwWriteChrct) {
|
|
265
|
+
this.forceNextBatteryEmission = true;
|
|
266
|
+
await writeGattCharacteristicValue(this.rwWriteChrct, new Uint8Array([0xb5]).buffer);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
else if (command.type === "REQUEST_FACELETS") {
|
|
270
|
+
if (this.lastFacelet) {
|
|
271
|
+
this.events$.next({
|
|
272
|
+
timestamp: now(),
|
|
273
|
+
type: "FACELETS",
|
|
274
|
+
facelets: this.lastFacelet
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
else if (command.type === "REQUEST_HARDWARE") {
|
|
279
|
+
this.emitHardwareEvent();
|
|
280
|
+
}
|
|
281
|
+
else if (command.type === "REQUEST_RESET") {
|
|
282
|
+
if (this.rwWriteChrct) {
|
|
283
|
+
await writeGattCharacteristicValue(this.rwWriteChrct, new Uint8Array([0xa1]).buffer);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async disconnect() {
|
|
288
|
+
if (this.dataChrct) {
|
|
289
|
+
this.dataChrct.removeEventListener('characteristicvaluechanged', this.onStateChanged);
|
|
290
|
+
await this.dataChrct.stopNotifications().catch(() => { });
|
|
291
|
+
this.dataChrct = null;
|
|
292
|
+
}
|
|
293
|
+
this.lastBatteryLevel = null;
|
|
294
|
+
this.forceNextBatteryEmission = false;
|
|
295
|
+
if (this.batteryInterval) {
|
|
296
|
+
clearInterval(this.batteryInterval);
|
|
297
|
+
this.batteryInterval = null;
|
|
298
|
+
}
|
|
299
|
+
if (this.rwReadChrct) {
|
|
300
|
+
if (this.onBatteryChanged) {
|
|
301
|
+
this.rwReadChrct.removeEventListener('characteristicvaluechanged', this.onBatteryChanged);
|
|
302
|
+
}
|
|
303
|
+
await this.rwReadChrct.stopNotifications().catch(() => { });
|
|
304
|
+
this.rwReadChrct = null;
|
|
305
|
+
}
|
|
306
|
+
this.onBatteryChanged = null;
|
|
307
|
+
this.rwWriteChrct = null;
|
|
308
|
+
this.device.removeEventListener('gattserverdisconnected', this.onDisconnect);
|
|
309
|
+
this.events$.next({ timestamp: now(), type: "DISCONNECT" });
|
|
310
|
+
this.events$.complete();
|
|
311
|
+
if (this.device.gatt?.connected) {
|
|
312
|
+
this.device.gatt.disconnect();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const giikerProtocol = {
|
|
317
|
+
nameFilters: [
|
|
318
|
+
{ namePrefix: "Gi" },
|
|
319
|
+
{ namePrefix: "Mi Smart Magic Cube" },
|
|
320
|
+
{ namePrefix: "Hi-" }
|
|
321
|
+
],
|
|
322
|
+
optionalServices: [SERVICE_UUID_DATA, SERVICE_UUID_RW],
|
|
323
|
+
matchesDevice(device) {
|
|
324
|
+
const name = device.name || '';
|
|
325
|
+
return name.startsWith('Gi') || name.startsWith('Mi Smart Magic Cube') || name.startsWith('Hi-');
|
|
326
|
+
},
|
|
327
|
+
gattAffinity(serviceUuids, _device) {
|
|
328
|
+
return serviceUuids.has(normalizeUuid(SERVICE_UUID_DATA)) ? 115 : 0;
|
|
329
|
+
},
|
|
330
|
+
async connect(device, _macProvider, _context) {
|
|
331
|
+
const devName = device.name || '';
|
|
332
|
+
const name = devName.startsWith('GiC') ? 'Giiker i3' :
|
|
333
|
+
devName.startsWith('GiS') ? 'Giiker i3S' :
|
|
334
|
+
devName.startsWith('GiY') ? 'Giiker i3Y' :
|
|
335
|
+
devName.startsWith('Mi Smart') ? 'Mi Smart Magic Cube' :
|
|
336
|
+
devName.startsWith('Gi') ? 'Giiker i3SE' :
|
|
337
|
+
devName.startsWith('Hi-') ? 'Hi-' :
|
|
338
|
+
devName || 'Unknown';
|
|
339
|
+
const conn = new GiikerConnection(device, name);
|
|
340
|
+
await conn.init();
|
|
341
|
+
return conn;
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
registerProtocol(giikerProtocol);
|
|
345
|
+
export { giikerProtocol };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { SmartCubeProtocol } from '../protocol.js';
|
|
2
|
+
/**
|
|
3
|
+
* MsgOrientation payload: ASCII decimals `x#y#z#w` (see public GoCube UART docs). Normalize to a unit quaternion.
|
|
4
|
+
* Integer components are scaled together so normalization yields the physical orientation.
|
|
5
|
+
*
|
|
6
|
+
* Wire `(rx,ry,rz,rw)` normalized to `(nx,ny,nz,nw)` then mapped to `(nx, -nz, -ny, nw)`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseGoCubeOrientationPayload(payloadUtf8: string): {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
z: number;
|
|
12
|
+
w: number;
|
|
13
|
+
} | null;
|
|
14
|
+
declare const goCubeProtocol: SmartCubeProtocol;
|
|
15
|
+
export { goCubeProtocol };
|