Real-time Melbourne public transport departures on your Pebble smartwatch.
YarraTrak shows live train, tram, and V/Line departure times directly on your wrist. Configure your favourite stations and get instant, glanceable info — with vibration patterns that encode the wait time, so you know when to leave without looking at the screen.
- Live departures — real-time data from the PTV API with second-by-second countdown
- Station watching — tap a favourite to open a full-screen countdown timer with platform info
- Smart vibrations — haptic patterns encode minutes until departure (hours/tens/ones)
- Vehicle tracking — see how far your train is from the station (metro trains)
- Multiple transport types — metro trains, V/Line, and trams
- Up to 10 favourites — configure your daily commute stations
- AI assistant (optional) — ask natural language questions via Claude ("next train from Richmond to the city")
- Install YarraTrak from the Rebble Appstore
- The app comes pre-loaded with 4 demo entries — open it and you'll see live departures immediately
- Open Settings on your phone to customise your favourite stations
- (Optional) Add an OpenRouter API key (sk-or-...) in Settings to enable the voice/text AI assistant
cd server
cp ../.env.example ../.env
# Edit .env with your PTV API credentials
pip install -r requirements.txt
uvicorn server.api:app --host 0.0.0.0 --port 8000| Variable | Required | Description |
|---|---|---|
PTV_DEV_ID |
Yes | PTV Timetable API developer ID |
PTV_API_KEY |
Yes | PTV Timetable API key |
PUBLIC_BASE_HOST |
No | Public Pebble/browser hostname. Default: ptv.netcavy.net |
INTERNAL_DASHBOARD_HOST |
No | Internal-only dashboard hostname. Default: ptv.yourinternal.website |
PUBLIC_APPSTORE_URL |
No | Redirect target for GET / on the public host. Default: https://apps.repebble.com/ |
METRICS_LOG_INTERVAL_SECONDS |
No | Metrics normalization/capture interval. Default: 60 |
METRICS_HISTORY_INTERVAL_SECONDS |
No | Dashboard history sampling interval. Default: 60 |
METRICS_HISTORY_MAX_POINTS |
No | Retained in-memory history points. Default: 10080 (7 days at 60s sampling) |
OPENROUTER_MODEL |
No | OpenRouter model slug used for agent queries. Default: deepseek/deepseek-v4-flash |
OPENROUTER_BASE_URL |
No | OpenRouter API base URL. Default: https://openrouter.ai/api/v1 |
Register for PTV API credentials at ptv.vic.gov.au.
cd pebble
pebble build
pebble install --phone YOUR_PHONE_IPpebble/ Native Pebble SDK 4.x watchapp
src/c/ C source (watch side)
main.c Entry point + window wiring
app_state.* Global state: entries, connection, watched run
departures.* Departure cache helpers
formatting.* Countdown / menu row text
haptics.* Minute-encoded vibration patterns
protocol.* AppMessage protocol (PKJS <-> C)
settings_store.* Persisted settings + favourites
ui/ splash, menu, watch, query windows + ripple layer + theme
src/pkjs/ PebbleKit JS companion
pebble-js-app.js WebSocket client, settings bridge, AI assistant
config/ Settings HTML page (stop picker, flags)
server/ FastAPI Python server
api.py REST + WebSocket endpoints
agent_engine.py AI agent
tools.py Agent tool implementations
ptv_client.py PTV Timetable API client
*.json Pre-built station databases
| Endpoint | Auth | Description |
|---|---|---|
GET /api/v1/stations |
Open | Station database (train/tram/vline) |
POST /api/v1/favourite |
Open | Quick departure check |
POST /api/v1/query |
BYOK | AI agent text query |
WS /ws |
Open | Real-time departures + agent queries |
GET /internal/metrics |
Internal host only | Latest normalized dashboard snapshot |
GET /internal/metrics/history |
Internal host only | Rolling in-memory time series |
BYOK = Bring Your Own Key. Pass llm_api_key in the POST /api/v1/query body or in each WebSocket query message.
The server collects anonymous metrics about query and request volumes so performance and scalability can be monitored over time.
ptv.yourinternal.websiteshould proxy to the same FastAPI service and serve the internal dashboard at/.ptv.netcavy.netshould keep/ws,/api/*, and/pebble-config.htmlunchanged, but redirect exactGET /andGET /index.htmltoPUBLIC_APPSTORE_URL.- Deny
/internal/*on the public nginx host and restrictptv.yourinternal.websiteto your internal or VPN CIDRs. - An example nginx config lives at
deploy/nginx/ptv.conf.example.
YarraTrak's vibration patterns encode the wait time haptically:
| Component | Duration | Example |
|---|---|---|
| Hours | 800ms buzz | 1 hour = one long buzz |
| Tens of minutes | 300ms buzz | 20 min = two medium buzzes |
| Ones | 80ms buzz | 3 min = three short buzzes |
So "1 hour 23 minutes" feels like: BUZZZZZ ... BUZ BUZ ... bz bz bz
The count rounds up mid-minute — rolling from 3:00 to 2:59 buzzes three times, rolling from 1:00 to 0:59 buzzes once. Under 30 seconds the countdown flips to NOW! and you get the "shave and a haircut" pattern.
If the ETA slips (a delay pushes the timer back), the countdown shakes left/right and a single long pulse fires alongside it so you feel the slip even if you're not looking at the watch.
The app ships with 4 pre-configured entries to demonstrate different transport types:
- Flinders St → Belgrave (Metro Train)
- Caulfield → Town Hall (Metro Train)
- Barkly Sq/Sydney Rd → QVM/Elizabeth St (Tram #19)
- Southern Cross → Bendigo (V/Line)
MIT
