11package io.rebble.libpebblecommon
22
3- import io.rebble.libpebblecommon.packets.PhoneAppVersion
43import io.rebble.libpebblecommon.exceptions.PacketDecodeException
5- import io.rebble.libpebblecommon.protocolhelpers.PacketRegistry
4+ import io.rebble.libpebblecommon.packets.PingPong
65import io.rebble.libpebblecommon.protocolhelpers.PebblePacket
76import 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}
0 commit comments