1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
package coffee.liz.rookery.bt
import kotlinx.coroutines.flow.Flow
/** Orientation quaternion; right-handed, +X red, +Y blue, +Z white (GAN convention). */
data class Quaternion(val x: Double, val y: Double, val z: Double, val w: Double)
/** Coarse angular velocity per axis as reported by the cube (small signed integers). */
data class AngularVelocity(val x: Int, val y: Int, val z: Int)
/** Corner/edge permutation and orientation as reported by the cube. */
data class CubeState(val cp: List<Int>, val co: List<Int>, val ep: List<Int>, val eo: List<Int>)
sealed interface SmartCubeEvent {
/**
* A face turn in WCA notation ("R", "U'").
* [cubeTimestampMs] is the cube's own clock (null if unknown, e.g. a move recovered from history);
* [hostTimestampMs] is the host's monotonic clock when the notification arrived.
* [recovered] marks moves that were missed live and filled in from the cube's move history,
* whose host timestamp is therefore not meaningful.
* [afterGap] marks the first move released after giving up on moves the cube never re-sent:
* some moves before it are missing, so trust the next [Facelets] over replaying moves.
*/
data class Move(
val move: String,
val cubeTimestampMs: Long?,
val hostTimestampMs: Long,
val serial: Int = -1,
val recovered: Boolean = false,
val afterGap: Boolean = false,
) : SmartCubeEvent
/** Kociemba-style 54-char facelets in U R F D L B order. */
data class Facelets(val facelets: String, val serial: Int = -1, val state: CubeState? = null) : SmartCubeEvent
data class Battery(val percent: Int) : SmartCubeEvent
data class Hardware(
val name: String?,
val hardwareVersion: String?,
val softwareVersion: String?,
val productDate: String? = null,
val gyroSupported: Boolean = false,
) : SmartCubeEvent
data class Gyro(val quaternion: Quaternion, val velocity: AngularVelocity? = null) : SmartCubeEvent
data object Disconnected : SmartCubeEvent
}
/** A connected smart cube. [events] is hot: collect it before issuing requests to see their replies. */
/** What a platform offers for finding and connecting smart cubes. */
interface CubeBackend {
/** Nearby smart cubes, as they're heard. Collect to scan; cancel to stop. */
fun scan(): Flow<BleAdvertisement>
/** [mac] is the cube's real MAC (needed for decryption) where the platform's [address] is something else. */
suspend fun connect(scope: kotlinx.coroutines.CoroutineScope, address: String, mac: String): SmartCube
}
interface SmartCube {
val name: String?
val address: String
val events: Flow<SmartCubeEvent>
suspend fun requestFacelets()
suspend fun requestBattery()
suspend fun requestHardware()
suspend fun resetToSolved()
suspend fun disconnect()
}
|