Skip to content

Commit 39bbc54

Browse files
committed
add app install packets
1 parent c349a6b commit 39bbc54

File tree

7 files changed

+334
-1
lines changed

7 files changed

+334
-1
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package io.rebble.libpebblecommon.packets
2+
3+
import io.rebble.libpebblecommon.protocolhelpers.PacketRegistry
4+
import io.rebble.libpebblecommon.protocolhelpers.PebblePacket
5+
import io.rebble.libpebblecommon.protocolhelpers.ProtocolEndpoint
6+
import io.rebble.libpebblecommon.structmapper.SUByte
7+
import io.rebble.libpebblecommon.structmapper.SUInt
8+
import io.rebble.libpebblecommon.structmapper.SUUID
9+
10+
sealed class AppFetchIncomingPacket() : PebblePacket(ProtocolEndpoint.APP_FETCH) {
11+
/**
12+
* Request command. See [AppFetchRequestCommand].
13+
*/
14+
val command = SUByte(m)
15+
16+
}
17+
18+
sealed class AppFetchOutgoingPacket(command: AppFetchRequestCommand) :
19+
PebblePacket(ProtocolEndpoint.APP_FETCH) {
20+
/**
21+
* Request command. See [AppFetchRequestCommand].
22+
*/
23+
val command = SUByte(m, command.value)
24+
25+
}
26+
27+
28+
/**
29+
* Packet sent from the watch when user opens an app that is not in the watch storage.
30+
*/
31+
class AppFetchRequest : AppFetchIncomingPacket() {
32+
33+
/**
34+
* UUID of the app to request
35+
*/
36+
val uuid = SUUID(m)
37+
38+
/**
39+
* ID of the app bank. Use in the [PutBytesAppInit] packet to identify this app install.
40+
*/
41+
val appId = SUInt(m, endianness = '<')
42+
}
43+
44+
/**
45+
* Packet sent from the watch when user opens an app that is not in the watch storage.
46+
*/
47+
class AppFetchResponse(
48+
status: AppFetchResponseStatus
49+
) : AppFetchOutgoingPacket(AppFetchRequestCommand.FETCH_APP) {
50+
/**
51+
* Response status
52+
*/
53+
val status = SUByte(m, status.value)
54+
55+
}
56+
57+
enum class AppFetchRequestCommand(val value: UByte) {
58+
FETCH_APP(0x01u)
59+
}
60+
61+
enum class AppFetchResponseStatus(val value: UByte) {
62+
/**
63+
* Sent right before starting to send PutBytes data
64+
*/
65+
START(0x01u),
66+
67+
/**
68+
* Sent when phone PutBytes is already busy sending something else
69+
*/
70+
BUSY(0x02u),
71+
72+
/**
73+
* Sent when UUID that watch sent is not in the locker
74+
*/
75+
INVALID_UUID(0x03u),
76+
77+
/**
78+
* Sent when there is generic data sending error (such as failure to read the local pbw file)
79+
*/
80+
NO_DATA(0x01u),
81+
}
82+
83+
84+
fun appFetchIncomingPacketsRegister() {
85+
PacketRegistry.register(
86+
ProtocolEndpoint.APP_FETCH,
87+
AppFetchRequestCommand.FETCH_APP.value
88+
) { AppFetchRequest() }
89+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package io.rebble.libpebblecommon.packets
2+
3+
import io.rebble.libpebblecommon.protocolhelpers.PacketRegistry
4+
import io.rebble.libpebblecommon.protocolhelpers.PebblePacket
5+
import io.rebble.libpebblecommon.protocolhelpers.ProtocolEndpoint
6+
import io.rebble.libpebblecommon.structmapper.SBytes
7+
import io.rebble.libpebblecommon.structmapper.SNullTerminatedString
8+
import io.rebble.libpebblecommon.structmapper.SUByte
9+
import io.rebble.libpebblecommon.structmapper.SUInt
10+
11+
sealed class PutBytesOutgoingPacket(command: PutBytesCommand) :
12+
PebblePacket(ProtocolEndpoint.PUT_BYTES) {
13+
/**
14+
* Request command. See [PutBytesCommand].
15+
*/
16+
val command = SUByte(m, command.value)
17+
18+
}
19+
20+
class PutBytesResponse : PebblePacket(ProtocolEndpoint.PUT_BYTES) {
21+
22+
/**
23+
* See [PutBytesResult]
24+
*/
25+
val result = SUByte(m)
26+
27+
/**
28+
* Cookie to send to all other put bytes requests
29+
*/
30+
val cookie = SUInt(m)
31+
}
32+
33+
/**
34+
* Send to init non-app related file transfer
35+
*/
36+
class PutBytesInit(
37+
objectSize: UInt,
38+
objectType: ObjectType,
39+
bank: UByte,
40+
filename: String
41+
) : PutBytesOutgoingPacket(PutBytesCommand.INIT) {
42+
val objectSize = SUInt(m, objectSize)
43+
val objectType = SUByte(m, objectType.value)
44+
val bank = SUByte(m, bank)
45+
val filename = SNullTerminatedString(m, filename)
46+
}
47+
48+
/**
49+
* Send to init app-specific file transfer.
50+
*/
51+
class PutBytesAppInit(
52+
objectSize: UInt,
53+
objectType: ObjectType,
54+
appId: UInt
55+
) : PutBytesOutgoingPacket(PutBytesCommand.INIT) {
56+
val objectSize = SUInt(m, objectSize)
57+
58+
// Object type in app init packet must have 8th bit set (?)
59+
val objectType = SUByte(m, objectType.value or (1u shl 7).toUByte())
60+
val appId = SUInt(m, appId)
61+
}
62+
63+
/**
64+
* Send file data to the watch. After every put you have to wait for response from the watch.
65+
*/
66+
class PutBytesPut(
67+
cookie: UInt,
68+
payload: UByteArray
69+
) : PutBytesOutgoingPacket(PutBytesCommand.PUT) {
70+
val cookie = SUInt(m, cookie)
71+
val payloadSize = SUInt(m, payload.size.toUInt())
72+
val payload = SBytes(m, payload.size, payload)
73+
}
74+
75+
/**
76+
* Sent when current file transfer is complete. [objectCrc] is the CRC32 hash of the sent payload.
77+
*/
78+
class PutBytesCommit(
79+
cookie: UInt,
80+
objectCrc: UInt
81+
) : PutBytesOutgoingPacket(PutBytesCommand.COMMIT) {
82+
val cookie = SUInt(m, cookie)
83+
val objectCrc = SUInt(m, objectCrc)
84+
}
85+
86+
/**
87+
* Send when there was an error during transfer and transfer cannot complete.
88+
*/
89+
class PutBytesAbort(
90+
cookie: UInt
91+
) : PutBytesOutgoingPacket(PutBytesCommand.ABORT) {
92+
val cookie = SUInt(m, cookie)
93+
}
94+
95+
/**
96+
* Send after app-related file was commited to complete install sequence
97+
*/
98+
class PutBytesInstall(
99+
cookie: UInt
100+
) : PutBytesOutgoingPacket(PutBytesCommand.INSTALL) {
101+
val cookie = SUInt(m, cookie)
102+
}
103+
104+
enum class PutBytesCommand(val value: UByte) {
105+
INIT(0x01u),
106+
PUT(0x02u),
107+
COMMIT(0x03u),
108+
ABORT(0x04u),
109+
INSTALL(0x05u)
110+
}
111+
112+
enum class PutBytesResult(val value: UByte) {
113+
ACK(0x01u),
114+
NACK(0x02u)
115+
}
116+
117+
enum class ObjectType(val value: UByte) {
118+
FIRMWARE(0x01u),
119+
RECOVERY(0x02u),
120+
SYSTEM_RESOURCE(0x03u),
121+
APP_RESOURCE(0x04u),
122+
APP_EXECUTABLE(0x05u),
123+
FILE(0x06u),
124+
WORKER(0x07u)
125+
}
126+
127+
fun putBytesIncomingPacketsRegister() {
128+
PacketRegistry.register(
129+
ProtocolEndpoint.PUT_BYTES,
130+
PutBytesResult.ACK.value,
131+
) { PutBytesResponse() }
132+
133+
PacketRegistry.register(
134+
ProtocolEndpoint.PUT_BYTES,
135+
PutBytesResult.NACK.value,
136+
) { PutBytesResponse() }
137+
}

src/commonMain/kotlin/io/rebble/libpebblecommon/protocolhelpers/PacketRegistry.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ object PacketRegistry {
2020
appmessagePacketsRegister()
2121
appRunStatePacketsRegister()
2222
musicPacketsRegister()
23+
appFetchIncomingPacketsRegister()
24+
putBytesIncomingPacketsRegister()
2325
}
2426

2527
/**
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.rebble.libpebblecommon.services
2+
3+
import io.rebble.libpebblecommon.ProtocolHandler
4+
import io.rebble.libpebblecommon.packets.AppFetchIncomingPacket
5+
import io.rebble.libpebblecommon.packets.AppFetchOutgoingPacket
6+
import io.rebble.libpebblecommon.protocolhelpers.PebblePacket
7+
import io.rebble.libpebblecommon.protocolhelpers.ProtocolEndpoint
8+
import kotlinx.coroutines.channels.Channel
9+
10+
class AppFetchService(private val protocolHandler: ProtocolHandler) : ProtocolService {
11+
val receivedMessages = Channel<AppFetchIncomingPacket>(Channel.BUFFERED)
12+
13+
init {
14+
protocolHandler.registerReceiveCallback(ProtocolEndpoint.APP_FETCH, this::receive)
15+
}
16+
17+
suspend fun send(packet: AppFetchOutgoingPacket) {
18+
protocolHandler.send(packet)
19+
}
20+
21+
fun receive(packet: PebblePacket) {
22+
if (packet !is AppFetchIncomingPacket) {
23+
throw IllegalStateException("Received invalid packet type: $packet")
24+
}
25+
26+
receivedMessages.offer(packet)
27+
}
28+
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.rebble.libpebblecommon.services
2+
3+
import io.rebble.libpebblecommon.ProtocolHandler
4+
import io.rebble.libpebblecommon.packets.PutBytesOutgoingPacket
5+
import io.rebble.libpebblecommon.packets.PutBytesResponse
6+
import io.rebble.libpebblecommon.protocolhelpers.PebblePacket
7+
import io.rebble.libpebblecommon.protocolhelpers.ProtocolEndpoint
8+
import kotlinx.coroutines.channels.Channel
9+
10+
class PutBytesService(private val protocolHandler: ProtocolHandler) : ProtocolService {
11+
val receivedMessages = Channel<PutBytesResponse>(Channel.BUFFERED)
12+
13+
init {
14+
protocolHandler.registerReceiveCallback(ProtocolEndpoint.PUT_BYTES, this::receive)
15+
}
16+
17+
suspend fun send(packet: PutBytesOutgoingPacket) {
18+
protocolHandler.send(packet)
19+
}
20+
21+
fun receive(packet: PebblePacket) {
22+
if (packet !is PutBytesResponse) {
23+
throw IllegalStateException("Received invalid packet type: $packet")
24+
}
25+
26+
receivedMessages.offer(packet)
27+
}
28+
29+
}

src/commonMain/kotlin/io/rebble/libpebblecommon/structmapper/types.kt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,24 @@ class SFixedString(mapper: StructMapper, size: Int, default: String = "") :
269269
}, mapper, size, default
270270
)
271271

272+
/**
273+
* Upload-only type that writes String as unbound null-terminated byte array.
274+
*/
275+
class SNullTerminatedString(mapper: StructMapper, default: String = "") :
276+
StructElement<String>(
277+
{ buf, el ->
278+
val bytes = el.get().encodeToByteArray()
279+
280+
buf.putBytes(
281+
bytes.toUByteArray()
282+
)
283+
buf.putUByte(0u)
284+
},
285+
{ buf, el ->
286+
throw UnsupportedOperationException("SNullTerminatedString is upload-only")
287+
}, mapper, 0, default
288+
)
289+
272290
/**
273291
* Represents arbitrary bytes in a struct
274292
* @param length the number of bytes, when serializing this is used to pad/truncate the provided value to ensure it's 'length' bytes long (-1 to disable this)
@@ -358,7 +376,7 @@ class SFixedList<T : Mappable>(
358376
}
359377

360378
override val size: Int
361-
get() = list.fold(0, {t,el -> t+el.size})
379+
get() = list.fold(0, { t, el -> t + el.size })
362380

363381
/**
364382
* Link the count of this element to the value of another struct element. Count will
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.rebble.libpebblecommon.util
2+
3+
import io.rebble.libpebblecommon.packets.ProtocolCapsFlag
4+
import io.rebble.libpebblecommon.packets.WatchVersion
5+
import io.rebble.libpebblecommon.protocolhelpers.ProtocolEndpoint
6+
7+
fun getMaxPebblePacketPayloadSize(
8+
endpoint: ProtocolEndpoint,
9+
watchVersion: WatchVersion.WatchVersionResponse?
10+
): Int {
11+
if (endpoint != ProtocolEndpoint.APP_MESSAGE) {
12+
return STANDARD_MAX_PEBBLE_PACKET_SIZE
13+
}
14+
15+
val capabilities = watchVersion?.capabilities?.let { ProtocolCapsFlag.fromFlags(it.get()) }
16+
17+
return if (capabilities?.contains(ProtocolCapsFlag.Supports8kAppMessage) == true) {
18+
8222
19+
} else {
20+
STANDARD_MAX_PEBBLE_PACKET_SIZE
21+
}
22+
}
23+
24+
fun getPutBytesMaximumDataSize(watchVersion: WatchVersion.WatchVersionResponse?): Int {
25+
// 4 bytes get used for the cookie
26+
return getMaxPebblePacketPayloadSize(ProtocolEndpoint.PUT_BYTES, watchVersion) - 4
27+
}
28+
29+
val STANDARD_MAX_PEBBLE_PACKET_SIZE = 2048

0 commit comments

Comments
 (0)