Skip to content

Forward third-party watchapp datalogging to companion apps (classic PebbleKit broadcasts) - #386

Open
Dreamkeeper wants to merge 1 commit into
coredevices:masterfrom
Dreamkeeper:companion-datalogging
Open

Forward third-party watchapp datalogging to companion apps (classic PebbleKit broadcasts)#386
Dreamkeeper wants to merge 1 commit into
coredevices:masterfrom
Dreamkeeper:companion-datalogging

Conversation

@Dreamkeeper

Copy link
Copy Markdown

Problem

Datalogging.logData() handles health tags and system-app tags, and silently drops everything else — so data a third-party watchapp logs never reaches its companion, even though the phone ACKs the sessions. For background workers this is the only phone-bound channel that exists (workers cannot use AppMessage), so worker-based apps currently cannot get any data off the watch without the user opening the watchapp.

Measured on a Pebble Time 2 + this app: a worker logging an item every 60 s (byte-array session, ACKed by the phone) delivered zero items to a registered companion receiver over 10+ minutes.

Change

  • CompanionDatalogging interface in commonMain with a session lifecycle (onSessionOpened / onDataItems / onSessionClosed), keyed by watch serial + session id since session ids are connection-local. DataLoggingService now passes through the watch-provided session timestamp and declared item type (previously parsed but discarded); Datalogging forwards non-health, non-system sessions.
  • Android implementation emits the classic PebbleKit com.getpebble.action.dl.RECEIVE_DATA ordered broadcast per item — same delivery style as the existing PebbleKitClassic AppMessage compatibility — with a random log UUID generated once per session, the real watch timestamp, and each item encoded per its declared type (byte-array → Base64 string extra, uint → long extra, int → int extra). FINISH_SESSION is emitted on close. Data ids come from a clock-seeded AtomicInteger so concurrent watches and process restarts don't repeat ids. Payloads that aren't a multiple of the item size log a warning and drop only the partial tail.
  • The bridge is split into a platform-independent session/encoding half (ClassicDataloggingSessions) and a thin intent transport; 13 host tests cover item decoding/encoding, multi-item and partial-tail splitting, session metadata stability, per-session UUIDs, close/finish, unknown-session and zero-item-size handling, cross-watch isolation, and data-id uniqueness.
  • iOS binds a no-op; the JVM platform module is still TODO upstream and is untouched.

No protocol or storage changes; the phone already ACKs these sessions, this just stops discarding the payloads.

Compatibility notes (deliberate trade-offs, happy to adjust)

  • Timestamp/tag/data extras use the primitive types (long/int) that PebbleKit ≥ 2.6 receivers expect (2.6 replaced Guava UnsignedInteger with long in the DataLogging API).
  • Best-effort by design (documented on the interface): items are forwarded as they arrive, nothing is buffered phone-side, and companion ACK_DATA is not consumed — if no receiver is running, a record is dropped even though the watch was ACKed. A delivery-guaranteed path would belong in PebbleKit2 as a proper API; this PR is the minimal bridge until then.
  • Broadcasts are unrestricted implicit broadcasts, matching the in-repo PebbleKitClassic AppMessage behavior. Two known consequences, flagged as a possible follow-up: any installed app can listen for third-party log data, and manifest-declared receivers on Android 8+ won't wake for them (runtime-registered receivers work, which a worker-based companion needs anyway). Targeting the companion package (intent.setPackage) from PBW/locker metadata would fix both — happy to do it here or in a follow-up, whichever you prefer.

Testing

  • 13 host tests (:libpebble3:testAndroidHostTest) on the platform-independent session/encoding logic.
  • Field-tested on a Pebble Time 2 + Android 15 (HyperOS) with a GPL-3.0 companion whose worker logs a 14-byte record every 60 s (https://github.com/Dreamkeeper/pebble-cryonics-monitor): records deliver through this build's forwarding — 13 records in the first session, median flush 236 s (the watch spools in ~4 min batches; firmware policy). Zero records with the stock app on the identical setup.

Disclosure

Developed with AI assistance (Claude). I reviewed the diff and field-tested it on real hardware, and I understand the code being changed. The use case is a real safety application (unresponsiveness monitor for cryonicists) where worker liveness matters.

🤖 Generated with Claude Code

Datalogging.logData() handled health and system-app tags and silently
dropped everything else, so data logged by third-party watchapps (in
particular background workers, which cannot use AppMessage and have no
other phone-bound channel) never reached their companions - even
though the sessions were ACKed.

Forward third-party sessions through a new session-scoped
CompanionDatalogging interface (opened/data/closed, keyed by watch
serial + session id since session ids are connection-local). Android
delivers via the classic PebbleKit com.getpebble.action.dl.* ordered
broadcasts, split into a platform-independent session/encoding half
(ClassicDataloggingSessions, unit-tested) and a thin intent transport:
one RECEIVE_DATA per item with a per-session random log UUID, the
watch-provided session timestamp, and the session's declared item type
(byte-array as Base64, uint as long, int as int - the representations
PebbleKit >= 2.6 receivers expect); FINISH_SESSION on close. Data ids
are atomic and clock-seeded so process restarts do not replay ids.
Payloads that are not a multiple of the item size log a warning and
drop only the partial tail. iOS binds a no-op; the JVM platform module
is still TODO upstream and is untouched.

Delivery is best-effort by design (documented on the interface): items
are forwarded as they arrive, nothing is buffered phone-side, and
companion ACK_DATA is not consumed. A delivery-guaranteed path would
belong in PebbleKit2 as a proper API; this is the minimal bridge.

Host tests cover item decoding/encoding, multi-item and partial-tail
payload splitting, session metadata stability, per-session UUIDs,
close/finish, unknown-session and zero-item-size handling, cross-watch
session-id isolation, and data-id uniqueness.

Field-tested on a Pebble Time 2 + Android 15: a background worker
logging one 14-byte record per minute delivered records through this
build (13 in the first session; the watch spools in ~4 min batches),
zero with the stock app.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant