Skip to content

Deliver third-party data logging to companion apps over PebbleKit 2 - #378

Merged
sjp4 merged 3 commits into
coredevices:masterfrom
neelts:datalog-pk2
Sep 17, 2026
Merged

sjp4 merged 3 commits into
coredevices:masterfrom
neelts:datalog-pk2

Conversation

@neelts

@neelts neelts commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Data logging sessions of third-party watchapps are dropped after the protocol-level ACK, so a watchapp cannot get its spooled data to a companion app (#160, #201). #292 tried this with the legacy Intent broadcasts and was closed in favor of PebbleKit 2. The API side landed in pebble-dev/PebbleKitAndroid2#37 and is released in io.rebble.pebblekit2:server 1.3.0.

Datalogging now emits third-party sessions as one ordered flow, thirdPartyEvents (Batch / Finished), instead of dropping them. Each event carries the session identity (UUID, tag, open timestamp), the item size and the watch serial.

On Android, PebbleKit2Datalogging collects the flow and calls the new sendOnDataLogReceived / sendOnDataLogSessionFinished.
One worker per session sends the batches in sequence, and sends the finish event after the companion app acknowledged all batches.
Nack, Unknown, null, a binder failure or a call timeout causes a limited number of retries; then the worker discards the rest of the session.
Delivery is not tied to a running watchapp, because spooled data arrives on reconnect while no watchapp is open.
The companion package comes from the watchapp appinfo.json (companionApp.android.apps[].package).
The delivery state machine is covered by unit tests (DatalogDeliveryTest).

I validated the flow end to end with an Intent-based prototype of this change (a fully offline session, 114/114 records received, zero loss), I will repeat the test on this PebbleKit 2 path and report here.

cc @sjp4 this is the PebbleKit 2 successor of #292

neelts and others added 2 commits August 23, 2026 11:29
Data logging sessions of third-party watchapps were dropped after the
protocol-level ACK. Datalogging now emits them as flows: thirdPartyData
(batches) and thirdPartyFinished (session end). The session timestamp
and the watch serial go with each record.

On Android, PebbleKit2Datalogging collects the flows and calls
sendOnDataLogReceived / sendOnDataLogSessionFinished (PebbleKit 2
1.3.0). One worker per session sends the batches in sequence and sends
the finish event after the companion app acknowledged all batches.
Nack, Unknown, null or a binder failure causes a limited number of
retries; then the worker discards the rest of the session. Delivery is
not tied to a running watchapp, because spooled data can arrive on
reconnect while no watchapp is open. The companion package comes from
the watchapp appinfo.json (companionApp.android.apps[].package).
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ neelts
❌ sjp4
You have signed the CLA already but the status is still pending? Let us recheck it.

@sjp4 sjp4 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you test with PK2? (PR description says you are going to, doesn't say whether you did)

}
}

private suspend fun companionPackagesFor(uuid: Uuid): List<String> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want to put this on Dispatchers.IO

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied the fix now

private suspend fun deliverWithRetry(send: suspend () -> Boolean): Boolean {
repeat(attempts) { attempt ->
val stored = try {
withTimeoutOrNull(sendTimeout) { send() } == true

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like PK2 doesn't support handling cancellation if this times out (so it will just get stuck here) - may be worth addressing in PK2

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks! something like this?
pebble-dev/PebbleKitAndroid2#51

The locker lookup and the PBW parse are blocking I/O.
@neelts

neelts commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

Did you test with PK2? (PR description says you are going to, doesn't say whether you did)

Not yet, only with the tests, will try this week

@neelts

neelts commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

Actually managed to test just now! All works end to end 🎉

@sjp4
sjp4 merged commit 2a62591 into coredevices:master Sep 17, 2026
2 of 3 checks passed
@Dreamkeeper

Copy link
Copy Markdown

Thanks @neelts, and thanks @sjp4 for steering my #386 here — this is the cleaner route.

A second data point from a different use case: a background worker (watchapp closed) logging one 16-byte record per minute as a liveness channel for an unresponsiveness alarm (Standby). Built master + this PR on Sep 17 and moved the companion to io.rebble.pebblekit2:client:1.3.1: records arrive via onDataLogReceived with the watchapp closed, routed by companionApp in the watchapp's package.json, first try, on a Pebble Time 2 + Android 16. Now switching to the store build that includes it.

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.

4 participants