Skip to content

Commit f175b0d

Browse files
authored
Merge pull request #7 from matejdro/phone_app_version
finish AppVersionResponse, update ProtocolHandler interface, finish time sync packets
2 parents ec9d101 + 4049326 commit f175b0d

File tree

39 files changed

+539
-196
lines changed

39 files changed

+539
-196
lines changed

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ kotlin {
154154
all {
155155
languageSettings {
156156
useExperimentalAnnotation('kotlin.RequiresOptIn')
157+
useExperimentalAnnotation('kotlin.ExperimentalUnsignedTypes')
158+
useExperimentalAnnotation('kotlin.ExperimentalStdlibApi')
157159
}
158160
}
159161
}

src/androidMain/kotlin/main.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,4 @@ package io.rebble.libpebblecommon
22

33
import io.rebble.libpebblecommon.packets.PhoneAppVersion
44

5-
@OptIn(ExperimentalUnsignedTypes::class)
65
actual fun getPlatform(): PhoneAppVersion.OSType = PhoneAppVersion.OSType.Android

src/androidMain/kotlin/util/DataBuffer.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package io.rebble.libpebblecommon.util
33
import java.nio.ByteBuffer
44
import java.nio.ByteOrder
55

6-
@OptIn(ExperimentalUnsignedTypes::class)
76
actual class DataBuffer {
87
private val actualBuf: ByteBuffer
98

src/commonMain/kotlin/io/rebble/libpebblecommon/BluetoothConnection.kt

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package io.rebble.libpebblecommon
2+
3+
enum class PacketPriority {
4+
NORMAL,
5+
LOW
6+
}

src/commonMain/kotlin/io/rebble/libpebblecommon/ProtocolHandler.kt

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,43 @@ package io.rebble.libpebblecommon
33
import io.rebble.libpebblecommon.protocolhelpers.PebblePacket
44
import io.rebble.libpebblecommon.protocolhelpers.ProtocolEndpoint
55

6-
@OptIn(ExperimentalUnsignedTypes::class)
76
interface ProtocolHandler {
87
/**
9-
* Send data to the watch. MUST be called within [withWatchContext]
8+
* Send data to the watch.
9+
*
10+
* @param priority Priority of the packet. Higher priority items will be sent before
11+
* low priority ones. Use low priority for background messages like sync and higher priority
12+
* for user-initiated actions that should be transmitted faster
13+
*
14+
* @return *true* if sending was successful, *false* if packet sending failed due to
15+
* unrecoverable circumstances (such as watch disconnecting completely).
1016
*/
11-
suspend fun send(packet: PebblePacket)
17+
suspend fun send(
18+
packet: PebblePacket,
19+
priority: PacketPriority = PacketPriority.NORMAL
20+
): Boolean
1221

1322
/**
14-
* Calls the specified block within watch sending context. Only one block within watch context
15-
* can be active at the same time, ensuring atomic bluetooth sending.
23+
* Send raw data to the watch.
24+
*
25+
* @param priority Priority of the packet. Higher priority items will be sent before
26+
* low priority ones. Use low priority for background messages like sync and higher priority
27+
* for user-initiated actions that should be transmitted faster
28+
*
29+
* @return *true* if sending was successful, *false* if packet sending failed due to
30+
* unrecoverable circumstances (such as watch disconnecting completely).
1631
*/
17-
suspend fun <T> withWatchContext(block: suspend () -> T): T
18-
fun registerReceiveCallback(endpoint: ProtocolEndpoint, callback: suspend (PebblePacket) -> Unit)
32+
suspend fun send(
33+
packetData: UByteArray,
34+
priority: PacketPriority = PacketPriority.NORMAL
35+
): Boolean
36+
37+
suspend fun startPacketSendingLoop(rawSend: suspend (UByteArray) -> Boolean)
38+
39+
fun registerReceiveCallback(
40+
endpoint: ProtocolEndpoint,
41+
callback: suspend (PebblePacket) -> Unit
42+
)
43+
44+
suspend fun receivePacket(bytes: UByteArray): Boolean
1945
}
Lines changed: 89 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,110 @@
11
package io.rebble.libpebblecommon
22

3-
import io.rebble.libpebblecommon.packets.PhoneAppVersion
43
import io.rebble.libpebblecommon.exceptions.PacketDecodeException
5-
import io.rebble.libpebblecommon.protocolhelpers.PacketRegistry
4+
import io.rebble.libpebblecommon.packets.PingPong
65
import io.rebble.libpebblecommon.protocolhelpers.PebblePacket
76
import io.rebble.libpebblecommon.protocolhelpers.ProtocolEndpoint
8-
import io.rebble.libpebblecommon.packets.PhoneAppVersion.ProtocolCapsFlag
9-
import io.rebble.libpebblecommon.packets.PingPong
10-
import kotlinx.coroutines.sync.Mutex
11-
import kotlinx.coroutines.sync.withLock
7+
import kotlinx.coroutines.*
8+
import kotlinx.coroutines.channels.Channel
9+
import kotlinx.coroutines.selects.select
10+
import kotlin.coroutines.coroutineContext
11+
import kotlin.jvm.Volatile
1212

1313
/**
1414
* Default pebble protocol handler
1515
*/
16-
@OptIn(ExperimentalUnsignedTypes::class)
17-
class ProtocolHandlerImpl(private val bluetoothConnection: BluetoothConnection) : ProtocolHandler {
18-
var protocolCaps: UInt = ProtocolCapsFlag.makeFlags(ProtocolCapsFlag.SupportsSendTextApp)
19-
16+
class ProtocolHandlerImpl() : ProtocolHandler {
2017
private val receiveRegistry = HashMap<ProtocolEndpoint, suspend (PebblePacket) -> Unit>()
21-
private val protocolMutex = Mutex()
18+
19+
private val normalPriorityPackets = Channel<PendingPacket>(Channel.BUFFERED)
20+
private val lowPriorityPackets = Channel<PendingPacket>(Channel.BUFFERED)
21+
22+
@Volatile
23+
private var idlePacketLoop: Job? = null
2224

2325
init {
24-
bluetoothConnection.setReceiveCallback(this::handle)
26+
startIdlePacketLoop()
27+
}
28+
29+
override suspend fun send(packetData: UByteArray, priority: PacketPriority): Boolean {
30+
val targetChannel = when (priority) {
31+
PacketPriority.NORMAL -> normalPriorityPackets
32+
PacketPriority.LOW -> lowPriorityPackets
33+
}
34+
35+
val callback = CompletableDeferred<Boolean>()
36+
targetChannel.send(PendingPacket(packetData, callback))
37+
38+
return callback.await()
39+
}
40+
41+
override suspend fun send(packet: PebblePacket, priority: PacketPriority): Boolean {
42+
return send(packet.serialize(), priority)
2543
}
2644

2745
/**
28-
* Send data to the watch. MUST be called within [withWatchContext]
46+
* Start a loop that will wait for any packets to be sent through [send] method and then
47+
* call provided lambda with byte array to send to the watch.
48+
*
49+
* Lambda should return *true* if sending was successful or
50+
* *false* if packet sending failed due to unrecoverable circumstances
51+
* (such as watch disconnecting completely)
52+
*
53+
* When lambda returns false, this method terminates
2954
*/
30-
override suspend fun send(packet: PebblePacket) {
31-
println("Sending on EP ${packet.endpoint}: ${packet.type}")
32-
bluetoothConnection.sendPacket(packet.serialize().toByteArray())
55+
override suspend fun startPacketSendingLoop(rawSend: suspend (UByteArray) -> Boolean) {
56+
idlePacketLoop?.cancelAndJoin()
57+
58+
try {
59+
while (coroutineContext.isActive) {
60+
// Receive packet first from normalPriorityPackets or from
61+
// lowPriorityPackets if there is no normal packet
62+
val packet = select<PendingPacket> {
63+
normalPriorityPackets.onReceive { it }
64+
lowPriorityPackets.onReceive { it }
65+
}
66+
67+
val success = rawSend(packet.data)
68+
69+
if (success) {
70+
packet.callback.complete(true)
71+
} else {
72+
packet.callback.complete(false)
73+
break
74+
}
75+
}
76+
} finally {
77+
startIdlePacketLoop()
78+
}
3379
}
3480

3581
/**
36-
* Calls the specified block within watch sending context. Only one block within watch context
37-
* can be active at the same time, ensuring atomic bluetooth sending.
82+
* Start idle loop when there is no packet sending loop active. This loop will just
83+
* reject all packets with false
3884
*/
39-
override suspend fun <T> withWatchContext(block: suspend () -> T): T {
40-
return protocolMutex.withLock {
41-
block()
85+
private fun startIdlePacketLoop() {
86+
idlePacketLoop = GlobalScope.launch {
87+
while (isActive) {
88+
val packet = select<PendingPacket> {
89+
normalPriorityPackets.onReceive { it }
90+
lowPriorityPackets.onReceive { it }
91+
}
92+
93+
packet.callback.complete(false)
94+
}
4295
}
4396
}
4497

45-
override fun registerReceiveCallback(endpoint: ProtocolEndpoint, callback: suspend (PebblePacket) -> Unit) {
98+
99+
override fun registerReceiveCallback(
100+
endpoint: ProtocolEndpoint,
101+
callback: suspend (PebblePacket) -> Unit
102+
) {
46103
val existingCallback = receiveRegistry.put(endpoint, callback)
47104
if (existingCallback != null) {
48105
throw IllegalStateException(
49-
"Duplicate callback registered for $endpoint: $callback, $existingCallback")
106+
"Duplicate callback registered for $endpoint: $callback, $existingCallback"
107+
)
50108
}
51109
}
52110

@@ -55,26 +113,14 @@ class ProtocolHandlerImpl(private val bluetoothConnection: BluetoothConnection)
55113
* @param bytes the raw pebble packet (including framing)
56114
* @return true if packet was handled, otherwise false
57115
*/
58-
private suspend fun handle(bytes: ByteArray): Boolean {
116+
override suspend fun receivePacket(bytes: UByteArray): Boolean {
59117
try {
60-
val packet = PebblePacket.deserialize(bytes.toUByteArray())
118+
val packet = PebblePacket.deserialize(bytes)
61119

62120
when (packet) {
63121
//TODO move this to separate service (PingPong service?)
64122
is PingPong.Ping -> send(PingPong.Pong(packet.cookie.get()))
65123
is PingPong.Pong -> println("Pong! ${packet.cookie.get()}")
66-
67-
is PhoneAppVersion.AppVersionRequest -> {
68-
val res = PhoneAppVersion.AppVersionResponse()
69-
res.protocolVersion.set(0xffffffffu)
70-
res.sessionCaps. set(0u)
71-
res.platformFlags. set(0u)
72-
res.majorVersion. set(2u)
73-
res.minorVersion. set(2u)
74-
res.bugfixVersion. set(0u)
75-
res.platformFlags. set(protocolCaps)
76-
send(res)
77-
}
78124
}
79125

80126
val receiveCallback = receiveRegistry[packet.endpoint]
@@ -85,10 +131,15 @@ class ProtocolHandlerImpl(private val bluetoothConnection: BluetoothConnection)
85131
receiveCallback.invoke(packet)
86132
}
87133

88-
}catch (e: PacketDecodeException){
134+
} catch (e: PacketDecodeException) {
89135
println("Warning: failed to decode a packet: '${e.message}'")
90136
return false
91137
}
92138
return true
93139
}
140+
141+
private class PendingPacket(
142+
val data: UByteArray,
143+
val callback: CompletableDeferred<Boolean>
144+
)
94145
}

src/commonMain/kotlin/io/rebble/libpebblecommon/main.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,4 @@ package io.rebble.libpebblecommon
22

33
import io.rebble.libpebblecommon.packets.PhoneAppVersion
44

5-
@OptIn(ExperimentalUnsignedTypes::class)
65
expect fun getPlatform(): PhoneAppVersion.OSType

src/commonMain/kotlin/io/rebble/libpebblecommon/packets/AppMessage.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import io.rebble.libpebblecommon.structmapper.*
88
import io.rebble.libpebblecommon.util.DataBuffer
99

1010

11-
@OptIn(ExperimentalUnsignedTypes::class, ExperimentalStdlibApi::class)
1211
class AppMessageTuple() : StructMappable() {
1312
enum class Type(val value: UByte) {
1413
ByteArray(0u),
@@ -190,7 +189,6 @@ class AppMessageTuple() : StructMappable() {
190189
}
191190
}
192191

193-
@OptIn(ExperimentalUnsignedTypes::class)
194192
sealed class AppMessage(message: Message, transactionId: UByte) : PebblePacket(endpoint) {
195193
val command = SUByte(m, message.value)
196194
val transactionId = SUByte(m, transactionId)
@@ -205,7 +203,6 @@ sealed class AppMessage(message: Message, transactionId: UByte) : PebblePacket(e
205203
AppMessageNACK(0x7fu)
206204
}
207205

208-
@OptIn(ExperimentalUnsignedTypes::class)
209206
class AppMessagePush(
210207
transactionId: UByte = 0u,
211208
uuid: Uuid = Uuid(0L, 0L),

src/commonMain/kotlin/io/rebble/libpebblecommon/packets/AppRunState.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import io.rebble.libpebblecommon.structmapper.SUByte
88
import io.rebble.libpebblecommon.structmapper.SUUID
99

1010

11-
@OptIn(ExperimentalUnsignedTypes::class)
1211
sealed class AppRunStateMessage(message: Message) : PebblePacket(endpoint) {
1312
val command = SUByte(m, message.value)
1413

@@ -22,7 +21,6 @@ sealed class AppRunStateMessage(message: Message) : PebblePacket(endpoint) {
2221
AppRunStateRequest(0x03u)
2322
}
2423

25-
@OptIn(ExperimentalUnsignedTypes::class)
2624
class AppRunStateStart(
2725
uuid: Uuid = Uuid(0L, 0L)
2826
) :
@@ -49,7 +47,6 @@ sealed class AppRunStateMessage(message: Message) : PebblePacket(endpoint) {
4947
}
5048
}
5149

52-
@OptIn(ExperimentalUnsignedTypes::class)
5350
class AppRunStateStop(
5451
uuid: Uuid = Uuid(0L, 0L)
5552
) :

0 commit comments

Comments
 (0)