Temporary PKJS watchPosition() speed feed (issue #13) #110
No reviewers
Labels
No labels
area:companion
area:docs
area:shared
area:tooling
area:watchapp
blocker
kind:chore
kind:feature
kind:spike
kind:test
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
robert/PedalPebble!110
Loading…
Reference in a new issue
No description provided.
Delete branch "area/pkjs-speed-feed"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #13.
What this does
Adds a temporary PKJS speed feed so the watchapp has real speed today, before any Android companion
work exists. Runs inside PebbleKit JS (the Pebble mobile app's own JS environment), not on the watch.
navigator.geolocation.watchPosition()withenableHighAccuracy: true,maximumAge: 0, and a15s
timeout; a realerrorcallback on every geolocation call (W3CPositionErrorcodes 1-3,all logged and treated the same way: tell the watch
SPEED_MMSis stale via the sentinel).SPEED_MMS/AVG_SPEED_MMS/DISTANCE_M.No raw lat/lon key exists anywhere in
docs/PROTOCOL.mdsection 2 (phone -> watch) —MAP_POSis unrelated per-slice map-tile geometry, not a live GPS feed — so "position forwarded to the
watch" is what position becomes on this wire today: distance and the two speed keys, not
coordinates.
companion/core'sSpeedPipeline/StopDetector(#17/#69): a single stopped-speed clamp (0.8 m/s— the same number as
StopDetector.kt'sSTOP_THRESHOLD_MPS, reused for consistency, not thehysteresis/dwell machinery around it), no GPS-quality gating, no wraparound-safe accumulators.
var, no arrow/let/const/template literals). pypkjs (this SDK'semulator,
pypkjs==2.0.7) runs PKJS on real V8 viaSTPyV8, so it would accept modern syntax, butI could not find current, official documentation of what JS engine the production Core Devices
Pebble mobile app embeds on a real phone, so I did not assume ES2020+ is safe there.
localStorage['pkjsSpeedFeedEnabled'],defaulting to enabled (Phase 1 has no other speed source). Issue #21 ("retire the PKJS feed behind
a fallback flag") is expected to flip it off, or change the default, once the real companion ships
its own telemetry — see
index.js's "FALLBACK FLAG" comment for exactly what that touches.index.js's top comment and indocs/DEV.md: backgroundreliability (PKJS has no hook tied to the watchapp being foregrounded, so Android's background
execution limits can throttle or kill it silently), no BLE sensors (phone-GPS-only), no GPS-quality
gating (single previous fix, no outlier rejection).
Message keys — where the numbers came from
SPEED_MMS(10) /AVG_SPEED_MMS(11) /DISTANCE_M(13) come fromwatchapp/package.json'smessageKeysobject, which the Core Devices SDK's ownprocess_message_keys.pybuild step turnsinto
build/js/message_keys.json(and a matchingMESSAGE_KEY_*C header) —index.jsreads themvia
require('message_keys'), not a literal. I confirmed this by inspecting the actual bundledbuild/pebble-js-app.jsoutput:module.exports = {"AVG_SPEED_MMS":11,"DISTANCE_M":13,"SPEED_MMS":10},matching
docs/PROTOCOL.mdsection 2.2 exactly.This is still a second, independent place those three integers are written down —
tools/gen_message_keys.py(#7) only generatesproto.handProto.kt, it has no JS target, sonothing checks this
package.jsonfragment againstshared/message_keys.jsonthe wayproto.h/Proto.ktare checked against each other. I hand-copied the three numbers with a comment inindex.jscitingdocs/PROTOCOL.mdsection 2.2 andshared/message_keys.jsondirectly. Extendingtools/gen_message_keys.pyto also emit/check thispackage.jsonfragment (or a standalone JSconstants module) would close that gap properly — flagging it as a follow-up rather than doing it in
this PR, since it touches a generator with its own tests and a companion Kotlin output I did not want
to risk regressing here.
Verified
pebble buildclean foremery,gabbroandbasalt(pebble clean && pebble build, all threeplatforms,
webpackbundlingsrc/pkjs/index.jswith no errors).emeryemulator run, not just a compile check:pebble install --emulator emery+pebble logsshowswatchPosition()getting a genuine fixthrough pypkjs's own IP-geolocation path (
pypkjs/javascript/navigator/geolocation.py: a realhttps://api.ipify.orglookup plus a bundledGeoLiteCity.dat, not a mock — this sandbox's networkegress works, confirmed with
curl), the resultingSPEED_MMS/AVG_SPEED_MMS/DISTANCE_MAppMessage arriving at the watch's C-side inbox handler (
main.c: AppMessage inbox: message received), and the watch's ack coming back to PKJS(
pkjs speed feed: sendAppMessage acked by watch (speed=0 avgSpeed=0 distance=0)). The 0s arecorrect — it's the first fix, no prior position to compute a delta from yet. Full log excerpt in
docs/DEV.md.pypkjs's ownwatchPosition()implementation (2.0.7) fires the success callback exactly once percall rather than actually repeating — a limitation of the emulator's geolocation stub, not of this
code — so a second, later fix was never exercised in this sandbox. Verified by reading the
accumulation logic instead; a real device (or a future pypkjs with a real recurring
watchPosition()) would exercise it.error/timeoutcallback path is implemented per the W3C Geolocation API shape but was notexercised live either: pypkjs's stub only calls its failure callback on an HTTP exception, and this
sandbox's network egress works, so no failure ever fired.
Files
watchapp/src/pkjs/index.js— newwatchapp/package.json—messageKeyschanged from the unused scaffold placeholder (["dummy"],list form, SDK auto-numbers those starting at 10000) to the pinned dict form above; added the
locationcapability (confirmed valid against the SDK's own schema,sdk-core/pebble/common/tools/schemas/attributes.json:["location", "configurable", "health"])docs/DEV.md— new "PebbleKit JS temporary speed feed" section;Project layoutupdatedhttps://claude.ai/code/session_01DAoXbRmJUf2uxNYBfdAXPt
Gives the watchapp real speed immediately via navigator.geolocation.watchPosition() running inside PebbleKit JS, before any Android companion work exists. - enableHighAccuracy: true, maximumAge: 0, a 15s timeout, and a real error callback on every geolocation call. - Speed/avg-speed/distance forwarded to the watch as SPEED_MMS / AVG_SPEED_MMS / DISTANCE_M. No raw lat/lon key exists anywhere in PROTOCOL.md section 2 (phone -> watch) to forward "position" as coordinates, so distance/speed derived from position is what "position forwarded to the watch" means here. - Distance and moving-time average accumulated on the JS side, deliberately simpler than companion/core's SpeedPipeline/StopDetector: a single stopped- speed clamp (0.8 m/s, the same number as StopDetector.kt's STOP_THRESHOLD_MPS), no GPS-quality gating, no hysteresis/dwell state machine. Written in plain ES5 since the production PKJS engine's real ECMAScript support isn't documented anywhere current-and-official. - Message keys come from watchapp/package.json's messageKeys object ({"SPEED_MMS": 10, "AVG_SPEED_MMS": 11, "DISTANCE_M": 13}), built by the SDK's own process_message_keys.py into build/js/message_keys.json and read via require('message_keys') in index.js -- confirmed by inspecting the actual bundled output, not assumed. Those three numbers are hand-copied from docs/PROTOCOL.md section 2.2 / shared/message_keys.json, since tools/gen_message_keys.py (#7) has no JS output target yet; flagged as a reasonable follow-up rather than done here. - Kept behind a fallback flag: localStorage['pkjsSpeedFeedEnabled'], default enabled. Issue #21 is expected to flip it off (or change the default) once the real companion ships its own telemetry. - Known limitations (background reliability, no BLE sensors, no GPS-quality gating) documented in index.js's top comment and in docs/DEV.md. Verified: `pebble build` clean for emery/gabbro/basalt, and a real pypkjs-driven emery emulator run -- watchPosition() got a genuine fix via pypkjs's own IP-geolocation path (api.ipify.org + GeoLiteCity.dat, not a mock), the resulting AppMessage was received by the watch's C-side inbox handler, and the watch's ack came back to PKJS. See docs/DEV.md for the full log excerpt. pypkjs's watchPosition() fires only once rather than repeating, so multi-fix accumulation was verified by reading the logic, not by a live run. Claude-Session: https://claude.ai/code/session_01DAoXbRmJUf2uxNYBfdAXPt