GRemoteX is a schema-driven remote control system for Garmin watches. It pairs a Connect IQ watch app with a PHP backend that renders screens, executes actions, and manages per-device bearer tokens.
gremotex/ Connect IQ watch app (Monkey C)
phpbackend/ PHP backend API and admin UI
This public repository keeps only templates and placeholder values. Real domains, credentials, and deployment settings should stay in untracked local files or in a private fork.
- The watch starts and tries to reuse a stored bearer token.
- If no token is available, it starts device pairing with
POST /auth/device/start. - The user approves the displayed code through the backend web UI.
- The watch polls
POST /auth/device/polluntil it receives a bearer token. - After authorization, the watch loads
/schema/app,/screen/{id}, and/action/{id}as a thin backend-driven client.
- Copy
phpbackend/public/into the public document root. - Keep
phpbackend/config/andphpbackend/data/outside the document root, or adjustAPP_ROOTinphpbackend/public/index.php. - Ensure the web server can write to
data/. - Copy
phpbackend/config/settings-template.phpto localphpbackend/config/settings.php. - Fill in your real domain, admin credentials, and any provider secrets in
phpbackend/config/settings.php. - Update
phpbackend/config/data.jsonwith your actual schema and data.
If you want the existing Backend: Deploy VS Code task and phpbackend/deploy-backend.ps1 to work without passing command-line arguments, copy phpbackend/deploy-backend.local.example.json to untracked phpbackend/deploy-backend.local.json and keep your real HostName, UserName, and TargetDir there.
Recommended server layout:
/var/www/example.com/
├── private/
│ └── gremotex/
│ ├── config/
│ │ ├── settings.php
│ │ └── data.json
│ ├── data/
│ │ ├── state.json
│ │ └── auth.sqlite
│ └── screen-modules/
└── web/
└── api/
├── .htaccess
├── grx.png
└── index.php
- Watch and application endpoints use
Authorization: Bearer <token>. - Device pairing starts unauthenticated and becomes authorized only after admin approval.
- Admin web pages use a signed-in session.
- Transitional automation endpoints use HTTP Basic auth with the same admin account.
- Bearer tokens are non-expiring by default until explicitly revoked.
| Method | Path | Auth | Purpose |
|---|---|---|---|
GET |
/health |
none | Liveness probe |
POST |
/auth/device/start |
none | Start device pairing |
POST |
/auth/device/poll |
none | Poll pairing status |
GET |
/auth/device/verify |
admin session | Approve pairing code |
GET |
/auth/devices |
admin session | View and manage devices |
GET |
/schema/app |
bearer token | Fetch app metadata and navigation |
GET |
/screen/{id} |
bearer token | Fetch a rendered screen |
POST |
/action/{id} |
bearer token | Execute an action |
GET |
/ping |
bearer token | Legacy compatibility endpoint |
GET |
/data |
bearer token | Legacy compatibility endpoint |
GET |
/data/{key} |
bearer token | Legacy compatibility endpoint |
Authorization header example:
Authorization: Bearer YOUR_ACCESS_TOKENStart pairing:
curl -X POST https://example.com/api/auth/device/start \
-H 'Content-Type: application/json' \
-d '{"deviceName":"GRemoteX Watch"}'Successful start response:
{
"ok": true,
"deviceId": "...",
"deviceCode": "...",
"userCode": "ABCD-EFGH",
"verificationUri": "https://example.com/auth/device/verify",
"verificationUriComplete": "https://example.com/auth/device/verify?code=ABCD-EFGH",
"expiresAt": "2026-06-06T13:32:50Z",
"intervalSec": 5,
"message": "Open the verification URL and approve the displayed user code."
}Poll pairing status:
curl -X POST https://example.com/api/auth/device/poll \
-H 'Content-Type: application/json' \
-d '{"deviceCode":"..."}'Pending response:
{
"ok": false,
"error": "authorization_pending",
"message": "Approval is still pending",
"intervalSec": 5,
"expiresAt": "..."
}Approved response:
{
"ok": true,
"deviceId": "...",
"deviceName": "FR965 Watch",
"accessToken": "grx_...",
"tokenType": "Bearer",
"approvedAt": "...",
"expiresAt": null
}Transitional automation example:
curl -u admin:YOUR_ADMIN_PASSWORD https://example.com/auth/device/approve \
-H 'Content-Type: application/json' \
-d '{"userCode":"ABCD-EFGH"}'- Do not ship shared secrets inside the watch app.
- Auth storage defaults to SQLite at
data/auth.sqlite. - The backend can later move to MySQL without changing the watch pairing contract.
- Admin password rotation can be done during deployment with:
.\phpbackend\deploy-backend.ps1 -PromptAdminPasswordOr with a prepared bcrypt hash:
.\phpbackend\deploy-backend.ps1 -AdminPasswordHash '$2y$12$...'The tracked watch source keeps https://example.com/api as a safe placeholder. Real backend URLs are injected only during local scripted builds.
Create a local, untracked config file:
Copy-Item .\gremotex\watch-config.local.example.json .\gremotex\watch-config.local.jsonSet your real backend URL:
{
"baseUrl": "https://your-real-host.example/api",
"developerKeyPath": "C:/path/to/developer_key.der",
"sdkBinPath": "C:/path/to/ConnectIQ/Sdk/bin"
}The PowerShell build and deploy scripts read this file and inject the backend URL into a temporary build copy of gremotex/source/Config.mc. The same local file can also hold your untracked developerKeyPath and sdkBinPath values for build and simulator workflows. If you run monkeyc directly against monkey.jungle, this local override is not applied.
Build the watch app:
.\gremotex\build-watch.ps1 -ProjectRoot .\gremotexRun in simulator:
.\gremotex\run-simulator.ps1 -ProjectRoot .\gremotex -BuildFirst -StartSimulatorDeploy to a connected watch:
.\gremotex\deploy-watch.ps1 -ProjectRoot .\gremotexYou can also keep using the existing VS Code tasks for build, simulator, and deploy workflows.
phpbackend/config/data.jsondefines screens, components, actions, and providers.phpbackend/screen-modules/contains shared PHP handlers used by screens and providers.- Tuya integration is supported through provider-backed screen modules and settings in
phpbackend/config/settings.php.
See phpbackend/SCHEMA-V1.md for the schema contract and provider model.
