個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Free, open-source smart trainer app: ERG workouts, real-road courses, open world, ride history, Garmin/Strava/TrainingPeaks/intervals.icu sync
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗A free, open-source smart-trainer app.
Just Watts connects to any Bluetooth FTMS smart trainer and heart rate strap, runs your workouts in ERG mode or lets you ride free by grade, watts or resistance level, and keeps your history: NP, TSS, zones, intervals, trends. It runs on your own computer, with no account and no API keys. The trainer connection lives in a small local server, so reloading or closing the browser never drops the trainer or the ride.
Try it in the browser, no install: justwatts.com (Chrome or Edge, Web Bluetooth; rides stay in the browser, no uploads). The local app below adds Garmin and Strava upload and a trainer connection that survives closing the tab.
Needs Python 3.10+ and a computer with Bluetooth LE. Any modern browser works for the page.
git clone https://github.com/yaportmax/justwatts
cd justwatts
pip install -e .
justwatts serve
Open http://127.0.0.1:8765, press Connect trainer,
pick it from the scan list. The server remembers it and reconnects by itself. A trainer accepts one
controller at a time: close Zwift or any other app using it first. start.cmd (Windows) or
./start.sh (macOS, Linux) starts the server and opens the browser.
No trainer? justwatts simulate starts a workout on the server's fake trainer; watch it in the page.
10m 45-75%, 5x(3m 115%, 3m 50%), 10m free),
.zwo import and export, an FTP ramp test.app/roads/marin, app/world) so a ride never waits on a server.web/api/_lib/social.js), no extra service. Visibility can be switched
off in Settings. The local app has no accounts, so none of this shows there.Common actions sit on the ride screen; the rest are in Adjust. Each change shows what changed, and intensity, watts, segment time, length, trainer mode and settings can be undone for 15 s.
| Control | Where | Key |
|---|---|---|
| Pause, resume (Pause during an auto-pause stays paused) | bar | Space |
| Intensity ±1 % (grade, watts or level when not in ERG) | bar −/+ | ↑ ↓ (Shift ±5) |
| Back (restart segment; in its first 10 s, the previous one), Skip | bar | ← → |
| Segment time ±1 min (±30 s in Adjust) | bar | [ ] |
| Segment watts ±5/±10 | Adjust | - = |
| Intensity presets, reset to 100 % | Adjust | |
| Workout length ±5 min | Adjust | |
| Trainer: ERG, or ERG off by grade or level (the plan keeps showing) | Adjust | 1 2 3 |
| Edit the plan from the current segment | Adjust | |
| After the plan: hold the last target or ride flat | Adjust | |
| Cadence on/off and range (off: no readout, target or cues), beeps | Adjust | |
| Map style (Map, Satellite, Topo, Cycling), 3D terrain, map or chart focus (courses with a route) | map | F |
| Open world: the way at the junction ahead (shown about 200 m before it) | tap | ← ↑ → |
| Open world: U-turn (ride back the way you came) | bar, junction prompt | ↓ |
| Open world: go to another spot, the ride goes on | bar (Spots) | P |
| Full screen | bar | Shift F |
| Undo, Save, End (two taps) | bar | Z, S |
| Reconnect trainer or HR | header |
? shows the keys. Free segments and a flat finish ride by grade, and −/+ moves it. FTP tests keep
their targets.
| Device | Support |
|---|---|
Smart trainers with Bluetooth FTMS (service 0x1826) | ERG, grade simulation, resistance level |
| Tested | Zwift Hub (server Bluetooth, survives page reloads) |
HR straps, Bluetooth Heart Rate service 0x180D | Yes; trainer-relayed HR is used if no strap. Straps that hide the HR service until they feel a heartbeat (seen on a Wahoo TRACKR) show "waiting for heartbeat" and connect once you wet the contacts. |
| ANT+ only devices | No |
Most current trainers (Wahoo, Tacx, Elite, Saris, JetBlack) expose FTMS; only the Zwift Hub has been tested so far. Resistance-level mode follows FTMS 1.0 and is untested on hardware.
Garmin (pip install "justwatts[garmin]", then justwatts login-garmin once). On first start with
Garmin linked, Just Watts imports your FTP and weight (only the ones you have not set yourself;
Import from Garmin in Settings redoes it and also brings HR zones). HR zones from Garmin are a
choice under Settings > HR zones; the default is % of max HR. It uses the unofficial
python-garminconnect library, which Garmin can
break at any time. Garmin is only active for the default data dir; JUSTWATTS_GARMIN=off or
serve --no-garmin disables it, and any other JUSTWATTS_DATA has it off unless JUSTWATTS_GARMIN=on.
Strava (no extra needed). Press Connect Strava in Settings and approve on Strava. This uses
Just Watts's Strava app: its client secret is not in this repo, it lives only in a tiny stateless token
service (web/api/strava/token.js) that swaps the sign-in code and refresh
tokens for access tokens. Uploads go straight from your computer to Strava; tokens stay in your data
folder. To skip the token service, open Use your own Strava API app in Settings and enter your own
Client ID and secret (strava.com/settings/api, callback domain localhost), or host the function
yourself and set JUSTWATTS_STRAVA_RELAY and JUSTWATTS_STRAVA_CLIENT_ID. Uploads go through the official API with the FIT file,
the ride name and a text summary (NP, IF, TSS, intervals, "Recorded with Just Watts"). If Garmin
Connect already syncs to Strava for you, uploading to both makes duplicates: pick one.
Share card: the ride summary renders a PNG card to download or share. Strava's API cannot attach photos, so add it by hand. If you set a public base URL in Settings (your own proxy or host), the Strava description links the card.
Every FIT file is saved to ~/.justwatts/rides/ before any upload, and History can always export
.fit and .tcx.
The server binds 127.0.0.1 and rejects any non-localhost Host header (the only exception is
/share/<ride>.png when you set a public base URL). The page proves itself with its own Origin
plus an X-HW: 1 header; the CLI uses a bearer token from a 0600 file. Details in
docs/PROTOCOL.md.
| Env | Default | |
|---|---|---|
JUSTWATTS_PORT | 8765 | server port |
JUSTWATTS_DATA | ~/.justwatts | settings, devices, rides, the ride in progress, logs, token, Strava tokens |
JUSTWATTS_GARMIN_TOKENS | ~/.garminconnect | Garmin session tokens |
JUSTWATTS_TOKEN | from <data>/token | API token for the CLI |
JUSTWATTS_GARMIN | auto | off disables Garmin, on forces it for a non-default data dir |
JUSTWATTS_BLE | on | off disables Bluetooth |
JUSTWATTS_STRAVA_RELAY | justwatts.com | Strava token service for the shared app; off allows only your own API app |
JUSTWATTS_STRAVA_CLIENT_ID | 168528 | Strava client ID used with that token service |
Older OPENSPIN_* and HW_* names still work. An existing ~/.openspin data dir keeps being used,
and older data dirs move to ~/.justwatts on first start.
pip install -e ".[dev]"
pytest
Tests run the browser code under Node (node 18+ on PATH), boot the full page against a real test
server, drive BLE through a fake bleak module, mock Garmin and Strava, force Garmin and Bluetooth
off, and check that a full ride works with none of the optional extras installed.
See SECURITY.md for the threat model and how to report a vulnerability.
MIT, see LICENSE. The vendored MapLibre GL JS 5.24.0 (app/vendor/, the 3D map) is BSD-3-Clause,
license next to it.