Skip to content

Add data logging delivery to companion apps - #37

Open
neelts wants to merge 1 commit into
pebble-dev:mainfrom
neelts:datalogging
Open

Add data logging delivery to companion apps#37
neelts wants to merge 1 commit into
pebble-dev:mainfrom
neelts:datalogging

Conversation

@neelts

@neelts neelts commented Aug 13, 2026

Copy link
Copy Markdown

Closes #1.

Problem

A watchapp can record data offline with data_logging_create() / data_logging_log(). The watch
spools the data to storage and sends it when the phone is connected. But PebbleKit2 has no API that
lets a companion app receive this data. The current Pebble app drains third-party sessions off the
watch and drops the data (see coredevices/mobileapp#292, which was redirected here).

Change

The server sends the data over the existing bound listener service, as two new actions on the
generic bundle protocol. There are no AIDL changes. Old and new libraries stay compatible.

  • DataLogSession (common-api): tag + start timestamp + itemSize. The watchapp UUID, the
    tag and the timestamp identify a session. Two recordings with the same tag stay distinct.
  • Client: BasePebbleListenerService.onDataLogReceived(watchappUUID, session, data, itemsLeft, watch): ReceiveResult and onDataLogSessionFinished(watchappUUID, session, watch): ReceiveResult.
    BaseJavaPebbleListenerService has callback-style wrappers.
  • Server: PebbleListenerConnector.sendOnDataLogReceived() / sendOnDataLogSessionFinished().
  • Samples (Kotlin + Java) and README / SERVER.MD sections.

Design notes:

  • Batches, not items: one call sends one watch batch (data.size / session.itemSize items). A
    large offline spool drains without a binder round-trip for each item. The contract requires whole
    items, sequential batches for each session, and at most 100 KB for one call (binder limit).
  • Ack = safe handover, bounded retry: the client returns Ack only after it stored the data.
    On Nack, Unknown or null the server keeps the data and sends it again later, a limited
    number of times; then it discards the data. An old client library answers the unknown action with
    an empty bundle. That decodes as ReceiveResult.Unknown, so the server can tell "old library"
    from "stored". Companions must store batches idempotently, so a retry is always safe.
  • Robustness: the client ignores malformed batches (bad fields, itemSize <= 0, split items)
    instead of crashing. Binder failures (for example TransactionTooLargeException) map to null.
    The Java wrappers answer Nack when the responder is not called in 30 seconds, so a companion
    bug cannot stall the server. The new connector methods have default implementations, so existing
    PebbleListenerConnector implementations stay source and binary compatible.
  • Data logging is not connected to an open watchapp. SERVER.MD tells the server to use the
    connector also outside the app opened/closed window. The bind operation wakes the companion app.

Open design questions

  • The selected-app gate (DefaultPebbleAndroidAppPicker) also filters data log delivery. With two
    Pebble apps installed, the auto-selected app can differ from the app that holds the watch
    connection, and delivery is then silently rejected. Messages have the same limitation today.
  • UniversalRequestResponse.request suspends with no binder death watch and no timeout, so a
    killed client process can stall a server coroutine. This also exists for messages; background
    data log delivery makes it more likely. A linkToDeath in SuspendingBindingConnection would
    fix both.
  • Session identity is UUID + tag + timestamp, with one-second resolution. The wire session id is
    transport-scoped and not usable as identity. Two sessions with the same tag made in the same
    second are not distinguishable.

Testing

  • All modules compile. detekt/ktlint show no findings.
  • A multi-agent code review ran on the diff; the fixes above (validation, bounded retry, binder
    failure handling, responder timeout, default implementations) come from it.
  • Open: an end-to-end test on hardware. I now implement the matching server side in
    coredevices/mobileapp (successor of #292). Then I test the full path with a real watchapp: a swim
    tracker records with phone Bluetooth off, and the spool arrives on reconnect.

@matejdro this implements #1 and I am happy to adjust the API shape.
@kescherCode does this cover your use case?
@Fauxmage this is the API that mobileapp#201 needed.
@sjp4 FYI, the matching mobileapp server side comes as a follow-up PR.

Sorry for mass-tagging 😅, I'm a simple man who really wants to track their swims 🏊

Data logging sessions of watchapps now get to companion apps. The
server sends batches and the session-finish event over the bound
listener service (new DATA_LOG_RECEIVED / DATA_LOG_SESSION_FINISHED
actions). A client overrides onDataLogReceived() /
onDataLogSessionFinished() and stores the data.

The Ack/Nack result makes the delivery safe. The companion sends Ack
only after it stored the data. So the Pebble app knows if it can
discard the data or must send it again. An old client library answers
with an empty bundle. That decodes as ReceiveResult.Unknown.
@kescherCode

Copy link
Copy Markdown

This does seem to cover my use-case at first glance, yes.

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.

Data logging support

2 participants