Spike A: PebbleKit Android round-trip with the new Pebble mobile app #4

Open
opened 2026-08-31 17:14:34 +02:00 by robert · 0 comments
robert commented 2026-08-31 17:14:34 +02:00 (Migrated from git.butzei.de)

Goal

The Core Devices mobile app is a rewrite and its PebbleKit-Android intent bridge is described only as "legacy support". The entire phone-to-watch design rests on it, so verify before building anything on top.

Acceptance criteria

  • Minimal watchapp sends one integer and receives one integer
  • Minimal Android app using com.getpebble:pebblekit:4.0.1 receives it via PebbleDataReceiver and replies with PebbleKit.sendDataToPebble()
  • PebbleKit.sendAckToPebble() acknowledged, no timeouts observed
  • isWatchConnected() and the connect/disconnect receivers behave correctly
  • Result written up in the issue: works / broken / partially works
  • If broken: PKJS + localhost HTTP fallback prototyped instead (watchapp XHRs http://127.0.0.1: against a server in the companion foreground service)

Notes

This is a blocker. Do not start Phase 2 until this is green or the fallback is proven.

Update — 2026-09-01: the likely failure mode is package visibility, not the bridge

This spike was written to answer "is the legacy bridge broken?" without knowing how it would break.
Checking the specifics first changes what it should test (D36):

  • com.getpebble:pebblekit:4.0.1 is on Maven Central, published 2016. The dependency itself is
    fine — the bintray/jcenter shutdown does not bite here.
  • The new app is coredevices.coreapp, and it registers the classic content provider
    com.getpebble.android.provider.basalt — which PebbleKit 4.0.1 does query
    (Constants.URI_CONTENT_BASALT). So the legacy bridge plausibly works.
  • The likely failure is Android 11+ package visibility. Without a <queries> element naming the
    Pebble app, isWatchConnected() returns false and broadcasts go nowhere. On API 31 — our minimum
    per D19 — that failure is silent, and it looks exactly like "the legacy bridge is broken".
  • There is a newer PebbleKit Android 2 path in libpebble3
    (io.rebble.libpebblecommon.pebblekit.two.PebbleKitProvider). Which of the two Core Devices
    intends third parties to use is an open question, and the answer decides #16.

Added criteria:

  • <queries> element declared for the Pebble app package, and the round-trip retested with
    and without it, so the effect is observed rather than assumed
  • Pebble app package name and provider authority confirmed on the actual installed build
  • PebbleKit Android 2 investigated: does a client-side artifact exist for third-party apps, and is
    it the supported path?
  • Recommendation recorded — classic PebbleKit, PebbleKit 2, or the PKJS fallback — with the
    evidence behind it
  • This spike may not report "broken" until package visibility has been ruled out. It gates
    more than thirty issues and should fail only for real reasons

If the PKJS localhost fallback is taken, note that as described it has no authentication: any app on
the phone could drive the watch and read the ride. That path carries a shared token generated at
pairing.

Update — 2026-09-02: PebbleKit 2 first, and two things this spike was not measuring

Checking the specifics again changes the answer, and adds scope (D37, D38).

PebbleKit Android 2 is a real, maintained project. The previous update recorded it as "an open
question" in libpebble3. It is pebble-dev/PebbleKitAndroid2,
a Kotlin library whose stated purpose is to replace 4.0.1's broadcast intents with bound services.
That is the better bet on both counts: it is the path being actively built, and a bound service is a
private channel where a broadcast is not.

The security point matters more than it first appears. NFR-S8 required the fallback transport to
authenticate, because otherwise any app on the phone could drive the watch and read the ride. 4.0.1's
broadcast intents have exactly that property, and the requirement gave the primary path a pass.
NFR-S8 is now general: no transport may be open to other apps on the phone.

  • PebbleKit Android 2 tried first; classic 4.0.1 second. Record which one Core Devices
    actually supports for third parties — the answer decides #16
  • Package visibility still ruled out before either may be reported broken (previous update stands)

The fallback was as unverified as the thing it backs up. PKJS support in a rewritten mobile app is
in question for precisely the same reason the intent bridge is, and nothing tested it. Worse, PKJS
plus a localhost XHR may not carry Phase 4 map traffic at all.

  • PKJS + localhost round-trip verified on the new mobile app, not assumed
  • Its sustained throughput measured, and judged against ~1 KB map slices (NFR-C13)

Nothing measured what the architecture costs in battery. NFR-B1 allows 25% of the watch over four
hours and is owned by #5, which measures the HRM. The design pushes ~1 Hz of AppMessage traffic for
four hours, and on a Pebble the radio is a larger drain than the e-paper. gabbro at two weeks of
standby is the binding case. This is the one number that could invalidate "the phone pushes
everything at 1 Hz", and no issue owned it.

  • Watch drain over a fixed window at idle, at 1 Hz ride metrics, and at 1 Hz plus map
    slices
    (NFR-B5)
  • Measured on gabbro as well as emery, since gabbro is the tighter case
## Goal The Core Devices mobile app is a rewrite and its PebbleKit-Android intent bridge is described only as "legacy support". The entire phone-to-watch design rests on it, so verify before building anything on top. ## Acceptance criteria - [ ] Minimal watchapp sends one integer and receives one integer - [ ] Minimal Android app using `com.getpebble:pebblekit:4.0.1` receives it via `PebbleDataReceiver` and replies with `PebbleKit.sendDataToPebble()` - [ ] `PebbleKit.sendAckToPebble()` acknowledged, no timeouts observed - [ ] `isWatchConnected()` and the connect/disconnect receivers behave correctly - [ ] Result written up in the issue: works / broken / partially works - [ ] If broken: PKJS + localhost HTTP fallback prototyped instead (watchapp XHRs http://127.0.0.1:<port> against a server in the companion foreground service) ## Notes **This is a blocker.** Do not start Phase 2 until this is green or the fallback is proven. ## Update — 2026-09-01: the likely failure mode is package visibility, not the bridge This spike was written to answer "is the legacy bridge broken?" without knowing how it would break. Checking the specifics first changes what it should test (D36): - **`com.getpebble:pebblekit:4.0.1` is on Maven Central**, published 2016. The dependency itself is fine — the bintray/jcenter shutdown does not bite here. - The new app is **`coredevices.coreapp`**, and it registers the classic content provider `com.getpebble.android.provider.basalt` — which PebbleKit 4.0.1 does query (`Constants.URI_CONTENT_BASALT`). So the legacy bridge plausibly works. - **The likely failure is Android 11+ package visibility.** Without a `<queries>` element naming the Pebble app, `isWatchConnected()` returns false and broadcasts go nowhere. On API 31 — our minimum per D19 — that failure is silent, and it looks exactly like "the legacy bridge is broken". - There is a newer **PebbleKit Android 2** path in `libpebble3` (`io.rebble.libpebblecommon.pebblekit.two.PebbleKitProvider`). Which of the two Core Devices intends third parties to use is an open question, and the answer decides #16. Added criteria: - [ ] `<queries>` element declared for the Pebble app package, and the round-trip **retested** with and without it, so the effect is observed rather than assumed - [ ] Pebble app package name and provider authority confirmed on the actual installed build - [ ] PebbleKit Android 2 investigated: does a client-side artifact exist for third-party apps, and is it the supported path? - [ ] Recommendation recorded — classic PebbleKit, PebbleKit 2, or the PKJS fallback — with the evidence behind it - [ ] **This spike may not report "broken" until package visibility has been ruled out.** It gates more than thirty issues and should fail only for real reasons If the PKJS localhost fallback is taken, note that as described it has no authentication: any app on the phone could drive the watch and read the ride. That path carries a shared token generated at pairing. ## Update — 2026-09-02: PebbleKit 2 first, and two things this spike was not measuring Checking the specifics again changes the answer, and adds scope (D37, D38). **PebbleKit Android 2 is a real, maintained project.** The previous update recorded it as "an open question" in `libpebble3`. It is [pebble-dev/PebbleKitAndroid2](https://github.com/pebble-dev/PebbleKitAndroid2), a Kotlin library whose stated purpose is to replace 4.0.1's **broadcast intents with bound services**. That is the better bet on both counts: it is the path being actively built, and a bound service is a private channel where a broadcast is not. The security point matters more than it first appears. NFR-S8 required the *fallback* transport to authenticate, because otherwise any app on the phone could drive the watch and read the ride. 4.0.1's broadcast intents have exactly that property, and the requirement gave the primary path a pass. NFR-S8 is now general: **no transport may be open to other apps on the phone.** - [ ] **PebbleKit Android 2 tried first**; classic 4.0.1 second. Record which one Core Devices actually supports for third parties — the answer decides #16 - [ ] Package visibility still ruled out before either may be reported broken (previous update stands) **The fallback was as unverified as the thing it backs up.** PKJS support in a rewritten mobile app is in question for precisely the same reason the intent bridge is, and nothing tested it. Worse, PKJS plus a localhost XHR may not carry Phase 4 map traffic at all. - [ ] PKJS + localhost round-trip verified **on the new mobile app**, not assumed - [ ] Its **sustained throughput** measured, and judged against ~1 KB map slices (NFR-C13) **Nothing measured what the architecture costs in battery.** NFR-B1 allows 25% of the watch over four hours and is owned by #5, which measures the *HRM*. The design pushes ~1 Hz of AppMessage traffic for four hours, and on a Pebble the radio is a larger drain than the e-paper. `gabbro` at two weeks of standby is the binding case. This is the one number that could invalidate "the phone pushes everything at 1 Hz", and no issue owned it. - [ ] Watch drain over a fixed window at **idle**, at **1 Hz ride metrics**, and at **1 Hz plus map slices** (NFR-B5) - [ ] Measured on `gabbro` as well as `emery`, since `gabbro` is the tighter case
Sign in to join this conversation.
No description provided.