Skip to content

Commit

Permalink
Merge pull request #7 from matejdro/phone_app_version
Browse files Browse the repository at this point in the history
finish AppVersionResponse, update ProtocolHandler interface, finish time sync packets
  • Loading branch information
crc-32 authored Nov 23, 2020
2 parents ec9d101 + 4049326 commit f175b0d
Show file tree
Hide file tree
Showing 39 changed files with 539 additions and 196 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ kotlin {
all {
languageSettings {
useExperimentalAnnotation('kotlin.RequiresOptIn')
useExperimentalAnnotation('kotlin.ExperimentalUnsignedTypes')
useExperimentalAnnotation('kotlin.ExperimentalStdlibApi')
}
}
}
Expand Down
1 change: 0 additions & 1 deletion src/androidMain/kotlin/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@ package io.rebble.libpebblecommon

import io.rebble.libpebblecommon.packets.PhoneAppVersion

@OptIn(ExperimentalUnsignedTypes::class)
actual fun getPlatform(): PhoneAppVersion.OSType = PhoneAppVersion.OSType.Android
1 change: 0 additions & 1 deletion src/androidMain/kotlin/util/DataBuffer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package io.rebble.libpebblecommon.util
import java.nio.ByteBuffer
import java.nio.ByteOrder

@OptIn(ExperimentalUnsignedTypes::class)
actual class DataBuffer {
private val actualBuf: ByteBuffer

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.rebble.libpebblecommon

enum class PacketPriority {
NORMAL,
LOW
}
40 changes: 33 additions & 7 deletions src/commonMain/kotlin/io/rebble/libpebblecommon/ProtocolHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,43 @@ package io.rebble.libpebblecommon
import io.rebble.libpebblecommon.protocolhelpers.PebblePacket
import io.rebble.libpebblecommon.protocolhelpers.ProtocolEndpoint

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

/**
* Calls the specified block within watch sending context. Only one block within watch context
* can be active at the same time, ensuring atomic bluetooth sending.
* Send raw data to the watch.
*
* @param priority Priority of the packet. Higher priority items will be sent before
* low priority ones. Use low priority for background messages like sync and higher priority
* for user-initiated actions that should be transmitted faster
*
* @return *true* if sending was successful, *false* if packet sending failed due to
* unrecoverable circumstances (such as watch disconnecting completely).
*/
suspend fun <T> withWatchContext(block: suspend () -> T): T
fun registerReceiveCallback(endpoint: ProtocolEndpoint, callback: suspend (PebblePacket) -> Unit)
suspend fun send(
packetData: UByteArray,
priority: PacketPriority = PacketPriority.NORMAL
): Boolean

suspend fun startPacketSendingLoop(rawSend: suspend (UByteArray) -> Boolean)

fun registerReceiveCallback(
endpoint: ProtocolEndpoint,
callback: suspend (PebblePacket) -> Unit
)

suspend fun receivePacket(bytes: UByteArray): Boolean
}
127 changes: 89 additions & 38 deletions src/commonMain/kotlin/io/rebble/libpebblecommon/ProtocolHandlerImpl.kt
Original file line number Diff line number Diff line change
@@ -1,52 +1,110 @@
package io.rebble.libpebblecommon

import io.rebble.libpebblecommon.packets.PhoneAppVersion
import io.rebble.libpebblecommon.exceptions.PacketDecodeException
import io.rebble.libpebblecommon.protocolhelpers.PacketRegistry
import io.rebble.libpebblecommon.packets.PingPong
import io.rebble.libpebblecommon.protocolhelpers.PebblePacket
import io.rebble.libpebblecommon.protocolhelpers.ProtocolEndpoint
import io.rebble.libpebblecommon.packets.PhoneAppVersion.ProtocolCapsFlag
import io.rebble.libpebblecommon.packets.PingPong
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.selects.select
import kotlin.coroutines.coroutineContext
import kotlin.jvm.Volatile

/**
* Default pebble protocol handler
*/
@OptIn(ExperimentalUnsignedTypes::class)
class ProtocolHandlerImpl(private val bluetoothConnection: BluetoothConnection) : ProtocolHandler {
var protocolCaps: UInt = ProtocolCapsFlag.makeFlags(ProtocolCapsFlag.SupportsSendTextApp)

class ProtocolHandlerImpl() : ProtocolHandler {
private val receiveRegistry = HashMap<ProtocolEndpoint, suspend (PebblePacket) -> Unit>()
private val protocolMutex = Mutex()

private val normalPriorityPackets = Channel<PendingPacket>(Channel.BUFFERED)
private val lowPriorityPackets = Channel<PendingPacket>(Channel.BUFFERED)

@Volatile
private var idlePacketLoop: Job? = null

init {
bluetoothConnection.setReceiveCallback(this::handle)
startIdlePacketLoop()
}

override suspend fun send(packetData: UByteArray, priority: PacketPriority): Boolean {
val targetChannel = when (priority) {
PacketPriority.NORMAL -> normalPriorityPackets
PacketPriority.LOW -> lowPriorityPackets
}

val callback = CompletableDeferred<Boolean>()
targetChannel.send(PendingPacket(packetData, callback))

return callback.await()
}

override suspend fun send(packet: PebblePacket, priority: PacketPriority): Boolean {
return send(packet.serialize(), priority)
}

/**
* Send data to the watch. MUST be called within [withWatchContext]
* Start a loop that will wait for any packets to be sent through [send] method and then
* call provided lambda with byte array to send to the watch.
*
* Lambda should return *true* if sending was successful or
* *false* if packet sending failed due to unrecoverable circumstances
* (such as watch disconnecting completely)
*
* When lambda returns false, this method terminates
*/
override suspend fun send(packet: PebblePacket) {
println("Sending on EP ${packet.endpoint}: ${packet.type}")
bluetoothConnection.sendPacket(packet.serialize().toByteArray())
override suspend fun startPacketSendingLoop(rawSend: suspend (UByteArray) -> Boolean) {
idlePacketLoop?.cancelAndJoin()

try {
while (coroutineContext.isActive) {
// Receive packet first from normalPriorityPackets or from
// lowPriorityPackets if there is no normal packet
val packet = select<PendingPacket> {
normalPriorityPackets.onReceive { it }
lowPriorityPackets.onReceive { it }
}

val success = rawSend(packet.data)

if (success) {
packet.callback.complete(true)
} else {
packet.callback.complete(false)
break
}
}
} finally {
startIdlePacketLoop()
}
}

/**
* Calls the specified block within watch sending context. Only one block within watch context
* can be active at the same time, ensuring atomic bluetooth sending.
* Start idle loop when there is no packet sending loop active. This loop will just
* reject all packets with false
*/
override suspend fun <T> withWatchContext(block: suspend () -> T): T {
return protocolMutex.withLock {
block()
private fun startIdlePacketLoop() {
idlePacketLoop = GlobalScope.launch {
while (isActive) {
val packet = select<PendingPacket> {
normalPriorityPackets.onReceive { it }
lowPriorityPackets.onReceive { it }
}

packet.callback.complete(false)
}
}
}

override fun registerReceiveCallback(endpoint: ProtocolEndpoint, callback: suspend (PebblePacket) -> Unit) {

override fun registerReceiveCallback(
endpoint: ProtocolEndpoint,
callback: suspend (PebblePacket) -> Unit
) {
val existingCallback = receiveRegistry.put(endpoint, callback)
if (existingCallback != null) {
throw IllegalStateException(
"Duplicate callback registered for $endpoint: $callback, $existingCallback")
"Duplicate callback registered for $endpoint: $callback, $existingCallback"
)
}
}

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

when (packet) {
//TODO move this to separate service (PingPong service?)
is PingPong.Ping -> send(PingPong.Pong(packet.cookie.get()))
is PingPong.Pong -> println("Pong! ${packet.cookie.get()}")

is PhoneAppVersion.AppVersionRequest -> {
val res = PhoneAppVersion.AppVersionResponse()
res.protocolVersion.set(0xffffffffu)
res.sessionCaps. set(0u)
res.platformFlags. set(0u)
res.majorVersion. set(2u)
res.minorVersion. set(2u)
res.bugfixVersion. set(0u)
res.platformFlags. set(protocolCaps)
send(res)
}
}

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

}catch (e: PacketDecodeException){
} catch (e: PacketDecodeException) {
println("Warning: failed to decode a packet: '${e.message}'")
return false
}
return true
}

private class PendingPacket(
val data: UByteArray,
val callback: CompletableDeferred<Boolean>
)
}
1 change: 0 additions & 1 deletion src/commonMain/kotlin/io/rebble/libpebblecommon/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@ package io.rebble.libpebblecommon

import io.rebble.libpebblecommon.packets.PhoneAppVersion

@OptIn(ExperimentalUnsignedTypes::class)
expect fun getPlatform(): PhoneAppVersion.OSType
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import io.rebble.libpebblecommon.structmapper.*
import io.rebble.libpebblecommon.util.DataBuffer


@OptIn(ExperimentalUnsignedTypes::class, ExperimentalStdlibApi::class)
class AppMessageTuple() : StructMappable() {
enum class Type(val value: UByte) {
ByteArray(0u),
Expand Down Expand Up @@ -190,7 +189,6 @@ class AppMessageTuple() : StructMappable() {
}
}

@OptIn(ExperimentalUnsignedTypes::class)
sealed class AppMessage(message: Message, transactionId: UByte) : PebblePacket(endpoint) {
val command = SUByte(m, message.value)
val transactionId = SUByte(m, transactionId)
Expand All @@ -205,7 +203,6 @@ sealed class AppMessage(message: Message, transactionId: UByte) : PebblePacket(e
AppMessageNACK(0x7fu)
}

@OptIn(ExperimentalUnsignedTypes::class)
class AppMessagePush(
transactionId: UByte = 0u,
uuid: Uuid = Uuid(0L, 0L),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import io.rebble.libpebblecommon.structmapper.SUByte
import io.rebble.libpebblecommon.structmapper.SUUID


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

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

@OptIn(ExperimentalUnsignedTypes::class)
class AppRunStateStart(
uuid: Uuid = Uuid(0L, 0L)
) :
Expand All @@ -49,7 +47,6 @@ sealed class AppRunStateMessage(message: Message) : PebblePacket(endpoint) {
}
}

@OptIn(ExperimentalUnsignedTypes::class)
class AppRunStateStop(
uuid: Uuid = Uuid(0L, 0L)
) :
Expand Down
Loading

0 comments on commit f175b0d

Please sign in to comment.