Skip to content

Commit 4885716

Browse files
zunda-pixelclaude
andcommitted
protocol: read the watch's refusals instead of dropping them
Endpoint 0 is the meta endpoint: the watch answers there when it will not answer on the endpoint that was addressed, with an error code and the id of the request it is about. Nothing decoded it, so PacketRegistry threw, the inbound loop swallowed the exception (its warning is commented out), and the refusal reached nobody. Whoever was waiting for the real answer therefore waited for a reply that was never coming. sendPing has no deadline of its own — unlike getFirmwareUpdateStatus, which does — so a ping to a watch in its recovery firmware never returned. That firmware refuses nearly every endpoint, and answers endpoint 0 with `DC 07 D1`: "endpoint 2001 is not handled here". MetaMessage now decodes it, and SystemService fails a pending ping when the watch says it will not answer one, the way WatchFactoryDataError already fails a pending watch-model request. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 4addc6d commit 4885716

3 files changed

Lines changed: 68 additions & 0 deletions

File tree

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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.SOptional
7+
import io.rebble.libpebblecommon.structmapper.SUByte
8+
import io.rebble.libpebblecommon.structmapper.SUShort
9+
import io.rebble.libpebblecommon.structmapper.StructMapper
10+
import io.rebble.libpebblecommon.util.Endian
11+
12+
/**
13+
* Meta endpoint (0x00, spelled [ProtocolEndpoint.RECOVERY]). The watch answers here when it will
14+
* not answer on the endpoint that was addressed: an [error] saying why, and the [rejectedEndpoint]
15+
* the refused request was for. A recovery firmware refuses nearly every endpoint, so there this is
16+
* the only reply most requests get.
17+
*/
18+
class MetaMessage : PebblePacket(endpoint) {
19+
/**
20+
* Why the request was refused. See [Error].
21+
*/
22+
val error = SUByte(m)
23+
24+
/**
25+
* The endpoint the refused request was addressed to. Absent for [Error.CorruptedMessage],
26+
* which the watch cannot attribute to an endpoint.
27+
*/
28+
val rejectedEndpoint =
29+
SOptional(m, SUShort(StructMapper(), endianness = Endian.Big), present = true)
30+
31+
enum class Error(val value: UByte) {
32+
NoError(0x00u),
33+
CorruptedMessage(0xd0u),
34+
Unhandled(0xdcu),
35+
Disallowed(0xddu),
36+
;
37+
38+
companion object {
39+
fun fromValue(value: UByte): Error? = entries.firstOrNull { it.value == value }
40+
}
41+
}
42+
43+
override fun toString(): String {
44+
val name = Error.fromValue(error.get())?.name ?: "0x${error.get().toInt().toString(16)}"
45+
return "MetaMessage(error=$name, rejectedEndpoint=${rejectedEndpoint.get()})"
46+
}
47+
48+
companion object {
49+
val endpoint = ProtocolEndpoint.RECOVERY
50+
}
51+
}
52+
53+
fun metaPacketsRegister() {
54+
PacketRegistry.register(MetaMessage.endpoint) { MetaMessage() }
55+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import io.rebble.libpebblecommon.packets.dataLoggingPacketsRegister
1515
import io.rebble.libpebblecommon.packets.getBytesIncomingPacketsRegister
1616
import io.rebble.libpebblecommon.packets.healthSyncPacketsRegister
1717
import io.rebble.libpebblecommon.packets.logDumpPacketsRegister
18+
import io.rebble.libpebblecommon.packets.metaPacketsRegister
1819
import io.rebble.libpebblecommon.packets.musicPacketsRegister
1920
import io.rebble.libpebblecommon.packets.phoneControlPacketsRegister
2021
import io.rebble.libpebblecommon.packets.putBytesIncomingPacketsRegister
@@ -34,6 +35,7 @@ object PacketRegistry {
3435
mutableMapOf()
3536

3637
init {
38+
metaPacketsRegister()
3739
systemPacketsRegister()
3840
timePacketsRegister()
3941
timelinePacketsRegister()

libpebble3/src/commonMain/kotlin/io/rebble/libpebblecommon/services/SystemService.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ import io.rebble.libpebblecommon.metadata.WatchColor.TimeRoundBlackSilverPolish2
1414
import io.rebble.libpebblecommon.metadata.WatchHardwarePlatform
1515
import io.rebble.libpebblecommon.metadata.WatchType.CHALK
1616
import io.rebble.libpebblecommon.packets.FirmwareProperty
17+
import io.rebble.libpebblecommon.packets.MetaMessage
1718
import io.rebble.libpebblecommon.packets.PhoneAppVersion
1819
import io.rebble.libpebblecommon.packets.PingPong
1920
import io.rebble.libpebblecommon.packets.ProtocolCapsFlag
2021
import io.rebble.libpebblecommon.packets.ResetMessage
2122
import io.rebble.libpebblecommon.packets.SystemMessage
2223
import io.rebble.libpebblecommon.packets.TimeMessage
2324
import io.rebble.libpebblecommon.packets.WatchFactoryData
25+
import io.rebble.libpebblecommon.protocolhelpers.ProtocolEndpoint
2426
import io.rebble.libpebblecommon.packets.WatchFirmwareVersion
2527
import io.rebble.libpebblecommon.packets.WatchVersion
2628
import io.rebble.libpebblecommon.packets.WatchVersion.WatchVersionResponse
@@ -213,6 +215,15 @@ class SystemService(
213215
pongCallback = null
214216
}
215217

218+
is MetaMessage -> {
219+
if (packet.rejectedEndpoint.get() == ProtocolEndpoint.PING.value) {
220+
pongCallback?.completeExceptionally(
221+
Exception("Watch will not answer a ping: $packet")
222+
)
223+
pongCallback = null
224+
}
225+
}
226+
216227
is TimeMessage.GetTimeUtcRequest-> {
217228
updateTime()
218229
}

0 commit comments

Comments
 (0)