summaryrefslogtreecommitdiff
path: root/core/src/commonMain/kotlin/coffee/liz/rookery/bt/SmartCube.kt
blob: 96e45e77a1703256c2a79539302bcef3f3254b91 (plain) (blame)
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()
}