Skip to content

Commit 3b0a337

Browse files
committed
Merge remote-tracking branch 'origin/master'
# Conflicts: # gradle/wrapper/gradle-wrapper.properties
2 parents 556e662 + a7768b1 commit 3b0a337

File tree

15 files changed

+462
-176
lines changed

15 files changed

+462
-176
lines changed

.github/workflows/pull.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
name: Build PR
2+
3+
on: [pull_request]
4+
5+
jobs:
6+
build:
7+
runs-on: macos-latest
8+
9+
steps:
10+
- name: Checkout source
11+
uses: actions/checkout@v1
12+
- name: Build
13+
run: ./gradlew build

build.gradle

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ plugins {
1515
}
1616

1717
def klockVersion = "1.12.0"
18-
def ktorVersion = "1.3.2"
19-
def kotlinxVersion = "1.3.9"
18+
def ktorVersion = "1.4.0"
19+
def coroutinesVersion = "1.3.9"
2020

2121
repositories {
2222
mavenCentral()
@@ -95,7 +95,7 @@ kotlin {
9595
jvmMain {
9696
dependencies {
9797
implementation kotlin('stdlib')
98-
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxVersion"
98+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
9999
implementation "com.soywiz.korlibs.klock:klock-jvm:$klockVersion"
100100
implementation "com.benasher44:uuid-jvm:0.2.2"
101101
}
@@ -108,7 +108,6 @@ kotlin {
108108

109109
implementation "io.ktor:ktor-client-websockets:$ktorVersion"
110110
implementation "io.ktor:ktor-client-cio:$ktorVersion"
111-
implementation "io.ktor:ktor-client-js:$ktorVersion"
112111
implementation "io.ktor:ktor-client-okhttp:$ktorVersion"
113112
}
114113
}
@@ -118,7 +117,7 @@ kotlin {
118117
implementation kotlin('stdlib-common')
119118
implementation "com.benasher44:uuid:0.2.2"
120119
implementation "com.soywiz.korlibs.klock:klock:$klockVersion"
121-
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxVersion"
120+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
122121
}
123122
}
124123
commonTest {
@@ -131,7 +130,7 @@ kotlin {
131130
androidMain {
132131
dependencies {
133132
implementation kotlin('stdlib')
134-
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxVersion"
133+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
135134
implementation "com.soywiz.korlibs.klock:klock-android:$klockVersion"
136135
implementation "com.benasher44:uuid-jvm:0.2.2"
137136
}
@@ -147,7 +146,14 @@ kotlin {
147146
iosMain {
148147
dependencies {
149148
implementation kotlin('stdlib')
150-
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxVersion"
149+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
150+
}
151+
}
152+
}
153+
sourceSets {
154+
all {
155+
languageSettings {
156+
useExperimentalAnnotation('kotlin.RequiresOptIn')
151157
}
152158
}
153159
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
#Thu May 07 14:16:13 BST 2020
2+
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip
13
distributionBase=GRADLE_USER_HOME
24
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
4-
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists
6+
zipStoreBase=GRADLE_USER_HOME
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+
interface BluetoothConnection {
4+
suspend fun sendPacket(data: ByteArray)
5+
fun setReceiveCallback(callback: suspend (ByteArray) -> Unit)
6+
}
Lines changed: 10 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,19 @@
11
package io.rebble.libpebblecommon
22

3-
import io.rebble.libpebblecommon.exceptions.PacketDecodeException
4-
import io.rebble.libpebblecommon.protocolhelpers.PacketRegistry
53
import io.rebble.libpebblecommon.protocolhelpers.PebblePacket
64
import io.rebble.libpebblecommon.protocolhelpers.ProtocolEndpoint
7-
import io.rebble.libpebblecommon.PhoneAppVersion.ProtocolCapsFlag
8-
import io.rebble.libpebblecommon.blobdb.BlobResponse
9-
import io.rebble.libpebblecommon.services.blobdb.BlobDBService
10-
11-
/**
12-
* Default pebble protocol handler
13-
* @param send callback to handle replying to packets
14-
*/
15-
@ExperimentalUnsignedTypes
16-
open class ProtocolHandler(private val send: (ByteArray) -> Unit) {
17-
var protocolCaps: UInt = ProtocolCapsFlag.makeFlags(ProtocolCapsFlag.SupportsSendTextApp)
18-
19-
init {
20-
PacketRegistry.setup()
21-
BlobDBService.init {packet -> _send(packet)}
22-
}
23-
24-
private fun _send(packet: PebblePacket) {
25-
println("Sending on EP ${packet.endpoint}: ${packet.type}")
26-
send(packet.serialize().toByteArray())
27-
}
285

6+
@OptIn(ExperimentalUnsignedTypes::class)
7+
interface ProtocolHandler {
298
/**
30-
* Handle a raw pebble packet
31-
* @param bytes the raw pebble packet (including framing)
32-
* @return true if packet was handled, otherwise false
9+
* Send data to the watch. MUST be called within [withWatchContext]
3310
*/
34-
open fun handle(bytes: ByteArray): Boolean {
35-
try {
36-
when (val packet = PebblePacket.deserialize(bytes.toUByteArray())) {
37-
is PingPong.Ping -> send(PingPong.Pong(packet.cookie.get()).serialize().toByteArray())
38-
is PingPong.Pong -> println("Pong! ${packet.cookie.get()}")
39-
40-
is PhoneAppVersion.AppVersionRequest -> {
41-
val res = PhoneAppVersion.AppVersionResponse()
42-
res.protocolVersion.set(0xffffffffu)
43-
res.sessionCaps. set(0u)
44-
res.platformFlags. set(0u)
45-
res.majorVersion. set(2u)
46-
res.minorVersion. set(2u)
47-
res.bugfixVersion. set(0u)
48-
res.platformFlags. set(protocolCaps)
49-
_send(res)
50-
}
11+
suspend fun send(packet: PebblePacket)
5112

52-
is BlobResponse -> return BlobDBService.receive(packet)
53-
54-
else -> {
55-
println("TODO: ${packet.endpoint}")
56-
return false
57-
}
58-
}
59-
}catch (e: PacketDecodeException){
60-
println("Warning: failed to decode a packet: '${e.message}'")
61-
return false
62-
}
63-
return true
64-
}
13+
/**
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.
16+
*/
17+
suspend fun <T> withWatchContext(block: suspend () -> T): T
18+
fun registerReceiveCallback(endpoint: ProtocolEndpoint, callback: suspend (PebblePacket) -> Unit)
6519
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package io.rebble.libpebblecommon
2+
3+
import io.rebble.libpebblecommon.exceptions.PacketDecodeException
4+
import io.rebble.libpebblecommon.protocolhelpers.PacketRegistry
5+
import io.rebble.libpebblecommon.protocolhelpers.PebblePacket
6+
import io.rebble.libpebblecommon.protocolhelpers.ProtocolEndpoint
7+
import io.rebble.libpebblecommon.PhoneAppVersion.ProtocolCapsFlag
8+
import kotlinx.coroutines.sync.Mutex
9+
import kotlinx.coroutines.sync.withLock
10+
11+
/**
12+
* Default pebble protocol handler
13+
*/
14+
@OptIn(ExperimentalUnsignedTypes::class)
15+
class ProtocolHandlerImpl(private val bluetoothConnection: BluetoothConnection) : ProtocolHandler {
16+
var protocolCaps: UInt = ProtocolCapsFlag.makeFlags(ProtocolCapsFlag.SupportsSendTextApp)
17+
18+
private val receiveRegistry = HashMap<ProtocolEndpoint, suspend (PebblePacket) -> Unit>()
19+
private val protocolMutex = Mutex()
20+
21+
init {
22+
PacketRegistry.setup()
23+
24+
bluetoothConnection.setReceiveCallback(this::handle)
25+
}
26+
27+
/**
28+
* Send data to the watch. MUST be called within [withWatchContext]
29+
*/
30+
override suspend fun send(packet: PebblePacket) {
31+
println("Sending on EP ${packet.endpoint}: ${packet.type}")
32+
bluetoothConnection.sendPacket(packet.serialize().toByteArray())
33+
}
34+
35+
/**
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.
38+
*/
39+
override suspend fun <T> withWatchContext(block: suspend () -> T): T {
40+
return protocolMutex.withLock {
41+
block()
42+
}
43+
}
44+
45+
override fun registerReceiveCallback(endpoint: ProtocolEndpoint, callback: suspend (PebblePacket) -> Unit) {
46+
val existingCallback = receiveRegistry.put(endpoint, callback)
47+
if (existingCallback != null) {
48+
throw IllegalStateException(
49+
"Duplicate callback registered for $endpoint: $callback, $existingCallback")
50+
}
51+
}
52+
53+
/**
54+
* Handle a raw pebble packet
55+
* @param bytes the raw pebble packet (including framing)
56+
* @return true if packet was handled, otherwise false
57+
*/
58+
private suspend fun handle(bytes: ByteArray): Boolean {
59+
try {
60+
val packet = PebblePacket.deserialize(bytes.toUByteArray())
61+
62+
when (packet) {
63+
//TODO move this to separate service (PingPong service?)
64+
is PingPong.Ping -> send(PingPong.Pong(packet.cookie.get()))
65+
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+
}
78+
}
79+
80+
val receiveCallback = receiveRegistry[packet.endpoint]
81+
if (receiveCallback == null) {
82+
//TODO better logging
83+
println("Warning, ${packet.endpoint} does not have receive callback")
84+
} else {
85+
receiveCallback.invoke(packet)
86+
}
87+
88+
}catch (e: PacketDecodeException){
89+
println("Warning: failed to decode a packet: '${e.message}'")
90+
return false
91+
}
92+
return true
93+
}
94+
}

src/commonMain/kotlin/io/rebble/libpebblecommon/blobdb/BlobDB.kt

Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import io.rebble.libpebblecommon.structmapper.SByte
77
import io.rebble.libpebblecommon.structmapper.SBytes
88
import io.rebble.libpebblecommon.structmapper.SUShort
99

10-
@ExperimentalUnsignedTypes
11-
open class BlobCommand(message: Message, token: UShort, database: BlobDatabase) : PebblePacket(
10+
@OptIn(ExperimentalUnsignedTypes::class)
11+
open class BlobCommand constructor(message: Message, token: UShort, database: BlobDatabase) : PebblePacket(
1212
endpoint
13-
){
13+
) {
1414
enum class Message(val value: UByte) {
1515
Insert(0x01u),
1616
Delete(0x04u),
@@ -31,26 +31,31 @@ open class BlobCommand(message: Message, token: UShort, database: BlobDatabase)
3131
val database = SByte(m, database.id)
3232

3333
open class InsertCommand(token: UShort, database: BlobDatabase, key: UByteArray, value: UByteArray) : BlobCommand(
34-
Message.Insert, token, database) {
34+
Message.Insert, token, database
35+
) {
3536
val keySize = SByte(m, key.size.toUByte())
3637
val targetKey = SBytes(m, key.size, key)
3738
val valSize = SUShort(m, value.size.toUShort(), endianness = '<')
3839
val targetValue = SBytes(m, value.size, value)
3940
}
41+
4042
class DeleteCommand(token: UShort, database: BlobDatabase, key: UByteArray) : BlobCommand(
41-
Message.Delete, token, database) {
43+
Message.Delete, token, database
44+
) {
4245
val keySize = SByte(m, key.size.toUByte())
4346
val targetKey = SBytes(m, key.size, key)
4447
}
48+
4549
class ClearCommand(token: UShort, database: BlobDatabase) : BlobCommand(
46-
Message.Clear, token, database)
50+
Message.Clear, token, database
51+
)
4752

4853
companion object {
4954
val endpoint = ProtocolEndpoint.BLOBDB_V1
5055
}
5156
}
5257

53-
@ExperimentalUnsignedTypes
58+
@OptIn(ExperimentalUnsignedTypes::class)
5459
open class BlobResponse(response: BlobStatus = BlobStatus.GeneralFailure) : PebblePacket(endpoint) {
5560
enum class BlobStatus(val value: UByte) {
5661
Success(0x01u),
@@ -66,37 +71,59 @@ open class BlobResponse(response: BlobStatus = BlobStatus.GeneralFailure) : Pebb
6671
TryLater(0xBu)
6772
}
6873

69-
class Success: BlobResponse(BlobStatus.Success)
70-
class GeneralFailure: BlobResponse(BlobStatus.GeneralFailure)
71-
class InvalidOperation: BlobResponse(BlobStatus.InvalidOperation)
72-
class InvalidDatabaseID: BlobResponse(BlobStatus.InvalidDatabaseID)
73-
class InvalidData: BlobResponse(BlobStatus.InvalidData)
74-
class KeyDoesNotExist: BlobResponse(BlobStatus.KeyDoesNotExist)
75-
class DatabaseFull: BlobResponse(BlobStatus.DatabaseFull)
76-
class DataStale: BlobResponse(BlobStatus.DataStale)
77-
class NotSupported: BlobResponse(BlobStatus.NotSupported)
78-
class Locked: BlobResponse(BlobStatus.Locked)
79-
class TryLater: BlobResponse(BlobStatus.TryLater)
74+
class Success : BlobResponse(BlobStatus.Success)
75+
class GeneralFailure : BlobResponse(BlobStatus.GeneralFailure)
76+
class InvalidOperation : BlobResponse(BlobStatus.InvalidOperation)
77+
class InvalidDatabaseID : BlobResponse(BlobStatus.InvalidDatabaseID)
78+
class InvalidData : BlobResponse(BlobStatus.InvalidData)
79+
class KeyDoesNotExist : BlobResponse(BlobStatus.KeyDoesNotExist)
80+
class DatabaseFull : BlobResponse(BlobStatus.DatabaseFull)
81+
class DataStale : BlobResponse(BlobStatus.DataStale)
82+
class NotSupported : BlobResponse(BlobStatus.NotSupported)
83+
class Locked : BlobResponse(BlobStatus.Locked)
84+
class TryLater : BlobResponse(BlobStatus.TryLater)
8085

8186
val token = SUShort(m)
8287
val response = SByte(m, response.value)
88+
8389
companion object {
8490
val endpoint = ProtocolEndpoint.BLOBDB_V1
8591
}
8692
}
8793

88-
@ExperimentalUnsignedTypes
94+
@OptIn(ExperimentalUnsignedTypes::class)
8995
fun blobDBPacketsRegister() {
9096
PacketRegistry.registerCustomTypeOffset(BlobResponse.endpoint, 4 + UShort.SIZE_BYTES)
91-
PacketRegistry.register(BlobResponse.endpoint, BlobResponse.BlobStatus.Success.value) {BlobResponse.Success()}
92-
PacketRegistry.register(BlobResponse.endpoint, BlobResponse.BlobStatus.GeneralFailure.value) {BlobResponse.GeneralFailure()}
93-
PacketRegistry.register(BlobResponse.endpoint, BlobResponse.BlobStatus.InvalidOperation.value) {BlobResponse.InvalidOperation()}
94-
PacketRegistry.register(BlobResponse.endpoint, BlobResponse.BlobStatus.InvalidDatabaseID.value) {BlobResponse.InvalidDatabaseID()}
95-
PacketRegistry.register(BlobResponse.endpoint, BlobResponse.BlobStatus.InvalidData.value) {BlobResponse.InvalidData()}
96-
PacketRegistry.register(BlobResponse.endpoint, BlobResponse.BlobStatus.KeyDoesNotExist.value) {BlobResponse.KeyDoesNotExist()}
97-
PacketRegistry.register(BlobResponse.endpoint, BlobResponse.BlobStatus.DatabaseFull.value) {BlobResponse.DatabaseFull()}
98-
PacketRegistry.register(BlobResponse.endpoint, BlobResponse.BlobStatus.DataStale.value) {BlobResponse.DataStale()}
99-
PacketRegistry.register(BlobResponse.endpoint, BlobResponse.BlobStatus.NotSupported.value) {BlobResponse.NotSupported()}
100-
PacketRegistry.register(BlobResponse.endpoint, BlobResponse.BlobStatus.Locked.value) {BlobResponse.Locked()}
101-
PacketRegistry.register(BlobResponse.endpoint, BlobResponse.BlobStatus.TryLater.value) {BlobResponse.TryLater()}
97+
PacketRegistry.register(BlobResponse.endpoint, BlobResponse.BlobStatus.Success.value) { BlobResponse.Success() }
98+
PacketRegistry.register(
99+
BlobResponse.endpoint,
100+
BlobResponse.BlobStatus.GeneralFailure.value
101+
) { BlobResponse.GeneralFailure() }
102+
PacketRegistry.register(
103+
BlobResponse.endpoint,
104+
BlobResponse.BlobStatus.InvalidOperation.value
105+
) { BlobResponse.InvalidOperation() }
106+
PacketRegistry.register(
107+
BlobResponse.endpoint,
108+
BlobResponse.BlobStatus.InvalidDatabaseID.value
109+
) { BlobResponse.InvalidDatabaseID() }
110+
PacketRegistry.register(
111+
BlobResponse.endpoint,
112+
BlobResponse.BlobStatus.InvalidData.value
113+
) { BlobResponse.InvalidData() }
114+
PacketRegistry.register(
115+
BlobResponse.endpoint,
116+
BlobResponse.BlobStatus.KeyDoesNotExist.value
117+
) { BlobResponse.KeyDoesNotExist() }
118+
PacketRegistry.register(
119+
BlobResponse.endpoint,
120+
BlobResponse.BlobStatus.DatabaseFull.value
121+
) { BlobResponse.DatabaseFull() }
122+
PacketRegistry.register(BlobResponse.endpoint, BlobResponse.BlobStatus.DataStale.value) { BlobResponse.DataStale() }
123+
PacketRegistry.register(
124+
BlobResponse.endpoint,
125+
BlobResponse.BlobStatus.NotSupported.value
126+
) { BlobResponse.NotSupported() }
127+
PacketRegistry.register(BlobResponse.endpoint, BlobResponse.BlobStatus.Locked.value) { BlobResponse.Locked() }
128+
PacketRegistry.register(BlobResponse.endpoint, BlobResponse.BlobStatus.TryLater.value) { BlobResponse.TryLater() }
102129
}

0 commit comments

Comments
 (0)