Poll Google Find Hub (formerly Find My Device) for device locations and battery levels, store a running history in Postgres (or SQLite), and publish updates to Discord via webhooks. Battery-low alerts for wearables (Pixel Watch, Pixel Buds) are supported with configurable thresholds.
Built on top of GoogleFindMyTools, which reverse-engineers Google's Nova/Spot API (gRPC + Firebase Cloud Messaging) to query device locations and decrypt E2EE location data.
┌─────────────────────────────────────────────────────────────┐
│ APScheduler │
│ ┌──────────────┐ ┌──────────────┐ ┌─────────────────────┐ │
│ │ Location Poll │ │ Battery Check│ │ Periodic Summary │ │
│ │ (5 min) │ │ (15 min) │ │ (6 hr) + Prune (24h)│ │
│ └──────┬───────┘ └──────┬───────┘ └──────────┬──────────┘ │
└─────────┼────────────────┼────────────────────┼─────────────┘
│ │ │
v v v
┌──────────────────┐ ┌──────────┐ ┌──────────────────┐
│ GoogleFindMyTools │ │ Battery │ │ Discord Webhooks │
│ (Nova API + FCM) │ │ Monitor │ │ (location/alerts)│
└────────┬─────────┘ └──────────┘ └──────────────────┘
│
v
┌──────────────────────────────────────┐
│ Database (Postgres primary / SQLite) │
│ devices | device_locations | alerts │
└──────────────────────────────────────┘
- Python 3.14+
- uv for package management
- Docker (for Postgres, optional — SQLite fallback available)
- Google Chrome (for one-time authentication only)
- A Google account with devices registered in Find Hub
- Discord webhook URL(s) for notifications
git clone https://github.com/karlmarx/find-hub-tracker.git
cd find-hub-tracker
uv syncGoogleFindMyTools is not on PyPI. Clone it and add to your PYTHONPATH:
git clone https://github.com/leonboe1/GoogleFindMyTools.git
export PYTHONPATH="$PWD/GoogleFindMyTools:$PYTHONPATH"Run the one-time authentication flow. This opens Chrome for you to log in:
find-hub-tracker auth
# or: uv run scripts/authenticate.pyThis creates Auth/secrets.json with your session tokens. This file is git-ignored and must never be committed. After initial auth, the service runs headlessly.
cp .env.example .env
# Edit .env with your Discord webhook URLs and database settingsWith Postgres (recommended):
docker compose up -d postgres
find-hub-tracker db-migrateWith SQLite (no Docker needed):
# Set DB_BACKEND=sqlite in .env
find-hub-tracker db-migratefind-hub-tracker startThe authentication flow uses GoogleFindMyTools to:
- Chrome login: Opens an embedded Google login page and waits for an OAuth token cookie
- AAS token: Exchanges the OAuth token for an Android Account Service token via
gpsoauth - FCM registration: Registers with Firebase Cloud Messaging for push notifications
- Device queries: Uses the AAS token to call Google's Nova API for device listing and location requests
- E2EE decryption: Location reports are end-to-end encrypted; the library decrypts them using your account's key chain
All tokens are cached in Auth/secrets.json for subsequent headless runs. If tokens expire, re-run find-hub-tracker auth.
| Variable | Default | Description |
|---|---|---|
DB_BACKEND |
postgres |
Database backend: postgres or sqlite |
DATABASE_URL |
postgresql://tracker:tracker@localhost:5432/find_hub_tracker |
Postgres connection string |
SQLITE_PATH |
./data/tracker_history.db |
SQLite database path (when DB_BACKEND=sqlite) |
DISCORD_WEBHOOK_URL |
(required) | Discord webhook for location updates |
DISCORD_BATTERY_WEBHOOK_URL |
(optional) | Separate webhook for battery alerts |
POLL_INTERVAL_SECONDS |
300 |
Location polling interval (5 min) |
BATTERY_CHECK_INTERVAL_SECONDS |
900 |
Battery check interval (15 min) |
SUMMARY_INTERVAL_HOURS |
6 |
Periodic summary interval |
BATTERY_LOW_THRESHOLD_PERCENT |
20 |
Low battery alert threshold |
BATTERY_CRITICAL_THRESHOLD_PERCENT |
10 |
Critical battery alert threshold |
WEARABLE_THRESHOLD_OFFSET |
5 |
Extra % added to thresholds for watches/buds |
ALERT_COOLDOWN_MINUTES |
60 |
Minimum time between repeated alerts per device |
HISTORY_RETENTION_DAYS |
90 |
Auto-prune records older than this |
AUTH_SECRETS_PATH |
./Auth/secrets.json |
Path to Google auth secrets |
LOG_LEVEL |
INFO |
Logging level |
DEVICES_TO_TRACK |
(empty = all) | Comma-separated device names to track |
Postgres is the primary backend because this is the first service in a planned centralized automation platform. Future services will share the same database.
docker compose up -d postgres
find-hub-tracker db-migrateFor quick local testing without Docker:
# In .env:
DB_BACKEND=sqlite
SQLITE_PATH=./data/tracker_history.dbBoth backends use the same schema and pass the same tests. The db.py module abstracts over both with a common DatabaseBackend protocol.
- Open your Discord server settings → Integrations → Webhooks
- Click New Webhook
- Name it (e.g., "Find Hub Tracker"), select a channel
- Copy the webhook URL and paste it into
.envasDISCORD_WEBHOOK_URL - (Optional) Create a second webhook in a different channel for battery alerts →
DISCORD_BATTERY_WEBHOOK_URL - Verify with:
find-hub-tracker test-discord
- Location updates (blue): Posted when a device moves >100m. Includes Google Maps link, distance moved, battery.
- Periodic summary (green): All device locations posted every 6 hours.
- Battery alerts (orange/red): Low and critical battery warnings. Wearables get a 5% higher threshold.
- Startup/shutdown (purple/grey): Service lifecycle messages.
docker compose up -d postgres # Start Postgres
cp .env.example .env # Configure
find-hub-tracker db-migrate # Create tables
find-hub-tracker auth # One-time Chrome auth
find-hub-tracker start # Run the pollerdocker compose --profile deploy up -dSee deploy/oracle-cloud-setup.md for step-by-step instructions on deploying to an always-free ARM VM.
See deploy/find-hub-tracker.service for a systemd unit file.
| Command | Description |
|---|---|
find-hub-tracker start |
Start the polling daemon |
find-hub-tracker status |
Show all devices and their last known locations |
find-hub-tracker history <device> [--days 7] |
Show location history for a device |
find-hub-tracker devices |
List all known devices with types and battery |
find-hub-tracker test-discord |
Send a test message to verify webhook config |
find-hub-tracker auth |
Run the Google authentication flow |
find-hub-tracker db-migrate |
Run pending database migrations |
find-hub-tracker db-prune [--days N] |
Manually prune old records |
uv run scripts/export_history.py --format csv --device "Pixel 9 Pro Fold" --days 30 --output locations.csv
uv run scripts/export_history.py --format json --output all_locations.jsonThis is the first service in a centralized automation platform. The Postgres database will be shared by future services including:
- Amex Claims Automator
- Scheduling/calendar automation
- Fitness/recovery tracking data aggregation
- Unofficial API: GoogleFindMyTools reverse-engineers Google's internal APIs. Google could change or block access at any time.
- Polling rate: Don't go below 5-minute intervals to avoid rate limiting or account flags.
- Battery data: GoogleFindMyTools does not currently expose battery levels. The monitoring infrastructure is built and ready — it will activate automatically when upstream adds support.
- Console output parsing: The GoogleFindMyTools wrapper parses stdout from the library. This is fragile and may break if the library changes its output format.
- E2EE key chain: Decrypting location data requires the full key chain from your Google account. If Google changes the encryption scheme, updates to GoogleFindMyTools will be needed.
- Auth expiry: Session tokens may expire. Re-run
find-hub-tracker authif you see authentication errors.
uv sync
uv run ruff check src/
uv run ruff format --check src/
uv run find-hub-tracker startMIT