Skip to content

Releases: futureweb/garmin-intervals-bridge

0.11.3 – swap in place, nothing deleted

Choose a tag to compare

@futureweb futureweb released this 15 Oct 09:06

Swap in place – the original into Garmin's copy, nothing deleted

Intervals.icu can now put a file into an existing activity – thanks David for building it so quickly. Swap mode uses it.

Added

  • Swap in place (BRIDGE_SWAP_METHOD=replace, the new default): swap mode puts the device's original into Garmin's copy itself (POST /activity/{id}/file).
    • The activity keeps its id and link, name, description, comments, hand-edited intervals, RPE, gear and tags.
    • Nothing is deleted.
    • Intervals computes every field and stream from the complete file, and Reprocess file keeps them.
    • Intervals still lists the activity as Garmin's import (source: GARMIN_CONNECT).
    • Thanks Lukasz_Pludowski for the question about edited intervals.
  • The past too: backfill --scope activities --from DATE --swap --apply swaps older activities in place, with no time limit.
  • One activity by hand: swap <activity> --method replace|upload.

Changed

  • Swap mode replaces by default. The previous way stays available as BRIDGE_SWAP_METHOD=upload: the original goes in as a new activity of your own, and Garmin's copy is deleted. You can choose it under How to swap on the web page's settings and in the Home Assistant app.
  • Charts: a chart the bridge adds (Actions → Charts, setup-charts) is now a copy of the same chart in Intervals' chart library, the way Intervals' own Copy makes it, so the library shows how many use it. Each copy stays your own; later changes to the library chart don't reach it.
  • The charts' description links the forum thread instead of GitHub.
  • setup and the web page say what "log in" means for the background bridge: it starts by itself whenever you log in to this computer (thanks Lukasz_Pludowski).

Fixed

  • A Gmail app password works as Google shows it, in groups with spaces, and without them. Before, the spaces made it fail.
  • The Gmail hint links Google's app-password page directly; the Security menu shows it only once you have one (thanks Lukasz_Pludowski).

Upgrading

pip install --upgrade garmin-intervals-bridge, pull the new image, or update the Home Assistant app.

  • With swap mode on: nothing to change; new activities are now swapped in place. To keep the old upload-and-delete way, set BRIDGE_SWAP_METHOD=upload.
  • Activities swapped before stay your uploads.
  • Charts you added before stay as they are.

0.11.2 – the Home Assistant app starts again

Choose a tag to compare

@futureweb futureweb released this 14 Oct 09:16

The Home Assistant app starts again

Fixed: with 0.11.1 the Home Assistant app stayed in "Starting" – the bridge ran fine, but the app could not be stopped or restarted from Home Assistant. 0.11.1 had put the container healthcheck into the image and turned it off for the app with HEALTHCHECK NONE; Home Assistant's Supervisor waits for a health result from any app whose image has a healthcheck entry at all, and with NONE none ever comes. The image carries no healthcheck now. compose.yaml, the Unraid template and the Synology guide set it instead (status --check every 5 minutes, as before). With a plain docker run: --health-cmd "garmin-intervals-bridge status --check" --health-interval 5m. Thanks DarKen.

Upgrading: Home Assistant shows the update. If the app's buttons are still blocked, restart Home Assistant once – the update then goes through. Docker and pip: nothing to do beyond the usual update; Unraid users may re-apply the template to get the healthcheck.

0.11.1 – the web page in Firefox, setup on Windows, a container that checks itself

Choose a tag to compare

@futureweb futureweb released this 14 Oct 08:31

The web page works in Firefox – and a container that says whether the bridge works

Fixed

  • The web page in Firefox: every button and form answered "foreign form". The page asked browsers not to send a referrer, and with that Firefox – as the web's Fetch standard says – sends a form's origin as null, which the check took for a foreign site. The page now keeps the referrer on its own pages, and a null origin counts as "no information"; the session's token and cookie still have to fit. Thanks Samantha_Lane and No_Cheetah in the forum.
  • Windows: a German Windows answered the web page's "Starts at login" check in its console code page, which ended in a UnicodeDecodeError in the console. That's read correctly now.
  • setup on Windows: in the classic PowerShell window Ctrl+V doesn't paste into the hidden API-key prompt but types the control character ^V, so Intervals refused the "key". Whatever a paste brings along (control characters, line breaks, quotes) is dropped now, the prompt says to paste with a right-click, and when too little arrives, it says so. Thanks Okocha in the forum.
  • A server briefly away:
    • A failed check for new activities counts for the pace, so during an outage the bridge tries again at the planned pace (at night every 30 minutes) rather than every minute.
    • Under systemd, a connection failure, a timeout or a 5xx is a warning, with no failed unit and no alert mail; a watch that keeps failing is still reported after 3 hours.
    • The log names the server and the reason (intervals.icu: too many 503 error responses).
  • Thanks @FrancescoCastaldi for these:
    • A setting left empty (BRIDGE_ACTIVITY_LOOKBACK_DAYS=) means its default instead of stopping the bridge.
    • A push message's title is always one line.
    • A FIT file may not declare more than 32 MB.

Added

  • The container image checks itself every 5 minutes (status --check, no request to anyone). Docker, Portainer, Unraid and Synology show it unhealthy when no run has come for about an hour; a break Intervals asked for, or a Garmin sign-in it's waiting for, count as healthy. The Home Assistant app leaves it out, since Home Assistant watches its apps itself. Thanks @FrancescoCastaldi for the idea.
  • The whole test suite runs on Windows and macOS too (thanks @FrancescoCastaldi).
  • The package declares its type hints (PEP 561 py.typed; thanks @FrancescoCastaldi).

Upgrading

pip install --upgrade garmin-intervals-bridge, pull the new image, or update the Home Assistant app. Nothing to change.

0.11.0 – fewer requests for Intervals.icu, and a second review

Choose a tag to compare

@futureweb futureweb released this 13 Oct 21:14

Fewer requests for Intervals.icu – and a second review of the whole code

Adaptive polling (beta)

BRIDGE_POLL_MODE=adaptive (in Home Assistant: Adaptive polling; on the web page: Settings) tries to keep the load on Intervals.icu's API as low as it can without making you wait. It learns from your own archive when your activities usually end: per weekday and hour, over the last twelve weeks, with recent weeks counting most. It then checks at your full pace only at those times, every few minutes around them, and every 30 minutes at times you hardly ever upload. For an hour after a new activity it is fast again.

It never checks faster than BRIDGE_POLL_SECONDS, and never outside BRIDGE_POLL_HOURS. Until 15 activities are in the archive, it keeps the full pace. The log and the web page show what it learned. On my own account it comes to about 390 checks a day instead of 1,440.

It is still beta – feedback is very welcome in the forum thread.

Also new

  • Quiet hours with a slower pace: BRIDGE_POLL_QUIET_SECONDS. Outside BRIDGE_POLL_HOURS the bridge checks this often instead of not at all; unset, it doesn't check, as before.
  • Check now on the web page: checks at once, then at the full pace for the next hour.
  • The status page shows when the bridge checks and why. The settings page sets the pace, the hours, the quiet pace and the adaptive mode, and so do the Home Assistant app and the Unraid template.
  • watch decides itself when a check is due, so the shipped systemd timer starts it every minute again. A watch that isn't due ends without a request.
  • Each FIT file's CRC is checked once instead of three times: about 40 % less CPU per new activity (thanks @Michael.0000 – noticeable on a Raspberry Pi).

A second review

An independent AI code review (OpenAI Codex) went over the whole code base. I checked every finding against the code and reproduced it, then fixed it – about 30 fixes. Most are places where a rare failure could have cost data. None is known to have happened.

  • Swap mode:
    • Garmin's copy is deleted only once your values read back from the original.
    • Comments are checked again right before every delete, retries included.
    • An upload whose answer got lost is never sent twice.
  • Wellness:
    • A read in which one Garmin endpoint failed keeps what the archive had.
    • A write to Intervals that failed is tried again.
  • Rate limits: a 429 from Intervals pauses the whole run for as long as Intervals asks, and a failing full run backs off instead of retrying every minute.
  • Enriching:
    • A value you typed in by hand is never overwritten.
    • Custom stream scripts are only reproduced when they're the plain copy-one-field form.
    • New custom fields are picked up without a restart.
  • Web page and settings: request time limits and a connection cap, password checks one at a time, and no setting can be saved that the bridge couldn't start with.

The details are in the CHANGELOG.

Upgrading

  • pip install --upgrade garmin-intervals-bridge, pull the new image, or update the Home Assistant app.
  • systemd users: copy the watch timer again. To keep checking every minute: BRIDGE_POLL_SECONDS=60.

0.10.3 – the Home Assistant app, fixed

Choose a tag to compare

@futureweb futureweb released this 13 Oct 18:38

The Home Assistant app's first real install, fixed – thanks @darken.

Fixed

  • "refused to connect" in the sidebar: the web page forbade being shown inside any frame – Home Assistant's own ingress frame included. Behind ingress, Home Assistant may frame it now (same origin only); everywhere else framing stays forbidden.
  • A data folder copied to share/garmin-bridge after the app's first start was never taken over (that start had left an empty database). It is taken over now as long as the app has no Garmin session and no archive of its own – once, and never over them.

Home Assistant offers the update itself; restart the app afterwards.

Install: pip install -U garmin-intervals-bridge (Windows: py -m pip install --upgrade garmin-intervals-bridge), or the image ghcr.io/futureweb/garmin-intervals-bridge:0.10.3 / futurewebat/garmin-intervals-bridge:0.10.3 (Docker Hub).

0.10.2 – kinder to Intervals.icu: a poll every 5 minutes

Choose a tag to compare

@futureweb futureweb released this 13 Oct 18:30

Kinder to Intervals.icu: a poll every 5 minutes, and a break when it asks for one.

Upgrading needs no change. To keep polling every minute: BRIDGE_POLL_SECONDS=60 (please only if you really need it). systemd users: the shipped watch timer now runs every 5 minutes; copy it again to take that over.

Changed

  • A poll every 5 minutes by default instead of every minute (run, autostart, containers, the Home Assistant app, the shipped systemd timer) – Intervals.icu asked self-hosted tools not to poll every minute. New activities still arrive within minutes.
  • A break when Intervals asks for one: an HTTP 429 pauses the polls and full runs for its Retry-After, at least 15 minutes; the systemd timers skip their runs meanwhile.
  • The container image's default command is run (keeps running, a dry run until --apply) instead of a one-time dry sync; every template set run already.

Documentation

  • A current roadmap; the rules contributions keep are in CONTRIBUTING.md.
  • The README lists every command, and how to install on Linux or with Homebrew's Python without --break-system-packages (pipx or a venv).

Install: pip install -U garmin-intervals-bridge (Windows: py -m pip install --upgrade garmin-intervals-bridge), or the image ghcr.io/futureweb/garmin-intervals-bridge:0.10.2 / futurewebat/garmin-intervals-bridge:0.10.2 (Docker Hub). Home Assistant shows the update itself.

0.10.1 – the bridge as a Home Assistant app

Choose a tag to compare

@futureweb futureweb released this 13 Oct 17:18

The bridge as a Home Assistant app – thanks @darken.

Added

  • Home Assistant app. Add https://github.com/futureweb/garmin-intervals-bridge to Home
    Assistant's app store (one click),
    install Garmin Intervals Bridge, fill in the form – API key, mode (enrich, upload or
    backup), Write to Intervals (off = a dry run first), swap, training advice, poll hours,
    alerts – and sign in to Garmin (MFA included) on the bridge's web page right in the
    sidebar. Home Assistant signs you in; nothing but its ingress proxy can reach the page. The
    time zone comes from Home Assistant. Moving in from a NAS: copy its data folder to
    share/garmin-bridge, and the app takes it over on its first start – no new Garmin sign-in.
    See docs/HOME_ASSISTANT.md.
  • An app's form (/data/options.json) now also covers mode, swap, poll hours and interval
    and the backup interval.

New: reports from real Home Assistant installs are very welcome.

Install: pip install -U garmin-intervals-bridge (Windows: py -m pip install --upgrade garmin-intervals-bridge), or the image ghcr.io/futureweb/garmin-intervals-bridge:0.10.1 / futurewebat/garmin-intervals-bridge:0.10.1 (Docker Hub).

0.10.0 – backup mode, a fuller backfill, a calm request counter

Choose a tag to compare

@futureweb futureweb released this 13 Oct 14:50

Backup mode – your Garmin data in your own folder, no Intervals needed – a fuller wellness
backfill, and a request counter that no longer scares anyone.

Added

  • Backup mode (BRIDGE_MODE=backup, run --mode backup, or answer 2 in setup):
    your Garmin Connect data in a folder of yours, kept current – no Intervals account needed,
    nothing written anywhere else. Every activity's original FIT with Garmin's summary and
    extras (weather, gear, splits, exercise sets); each day's wellness – the full answers of
    all ~30 endpoints plus the watch's own wellness files (Health Snapshots included) – once
    the day is final and again three days later for a late sync; the account weekly. A run
    every 12 hours (BRIDGE_BACKUP_HOURS), about 60 Garmin requests a day.
    backup --from 2020-01-01 fills in the past, next to a running bridge. Moving to
    Intervals later? The archive is exactly what its modes use. See
    docs/BACKUP.md.

Changed

  • A wellness backfill also reads endurance & hill score and race predictions by default
    (15 instead of 12 requests per day; --endpoints essential for the old set) – thanks
    @jetty. For days backfilled before:
    --endpoints endurance_score,hill_score,race_predictions --force-wellness.
  • Swap mode's wording says plainly that the deleted copy is the one in Intervals – nothing
    on Garmin Connect is ever touched (thanks @darken).

Fixed

  • The daily summary ("last 24 h … Garmin requests" on the web page and in the health log)
    counted far too many Garmin requests when the bridge runs as run: every minute's check
    recorded the whole request count of the last full run again – 50 real requests showed up
    as 2,000. Only the count was wrong, not the requests – thanks @Jeroen_Doesburg.

Install: pip install -U garmin-intervals-bridge (Windows: py -m pip install --upgrade garmin-intervals-bridge), or the image ghcr.io/futureweb/garmin-intervals-bridge:0.10.0 / futurewebat/garmin-intervals-bridge:0.10.0 (Docker Hub).

0.9.1 – poll less, or only in certain hours

Choose a tag to compare

@futureweb futureweb released this 12 Oct 17:50

Poll less, or only in certain hours – thanks @Michael.0000.

Added

  • BRIDGE_POLL_SECONDS – seconds between the polls of run (default 60, upload mode 600;
    --poll-seconds still wins). BRIDGE_POLL_SECONDS=300 polls every five minutes.
  • BRIDGE_POLL_HOURS – poll only in these hours: 10-18, several ranges like 6-9,16-22,
    or over midnight like 22-6. Outside them run skips its polls and watch does nothing;
    the half-hourly full runs go on, so an activity recorded outside the window still arrives
    (within about two hours instead of a minute) and so does last night's wellness.

The real end of polling would be a webhook from Intervals – see the
feature request.

Install: pip install -U garmin-intervals-bridge (Windows: py -m pip install --upgrade garmin-intervals-bridge), or the image ghcr.io/futureweb/garmin-intervals-bridge:0.9.1 / futurewebat/garmin-intervals-bridge:0.9.1 (Docker Hub).

0.9.0 – swap mode: Garmin's filtered copy out, the original in

Choose a tag to compare

@futureweb futureweb released this 12 Oct 17:34

Swap mode: Garmin's filtered copy out, the original in – so Intervals holds the complete file
and "Reprocess file" keeps everything.

Upgrading needs no change; swap mode is off until you switch it on (BRIDGE_SWAP=true, a
question in setup, a switch on the web page).

Added

  • Swap mode (BRIDGE_SWAP=true, off by default). The official Garmin import stays on
    and is the trigger: within a minute of Garmin's filtered copy arriving, the bridge
    uploads the device's original as your own file, carries over what you may have set
    already (name, description, paired workout, type, RPE, feel, tags, gear, commute/race,
    ignore settings, custom fields without a script such as the logged intake) and then
    deletes the copy Garmin's import created in Intervals – nothing on Garmin Connect is
    touched, the bridge only ever reads from Garmin. Intervals computes every custom field and stream from the
    complete file itself, Reprocess file keeps them, nothing is marked as edited, and
    the activity is your own upload rather than Garmin-sourced data. No polling of Garmin
    – one original download per new activity, as before – and with the bridge down the
    copy simply stays and is completed or swapped later.
  • The only place the bridge deletes anything, so it is careful: Garmin's own import
    only, within BRIDGE_SWAP_HOURS (48) of the import, never with comments or
    hand-edited intervals on it, not when Garmin sent the unfiltered file anyway; upload
    first, delete last – a failure leaves a duplicate to see, never a loss. The new
    activity has a new id.
  • swap <id or link> [--apply]: the same for one activity by hand, also an older one.

Changed

  • enrich <id> --apply and the web page's One activity again also put the logged
    intake back (GarminFluidIntake, GarminCaloriesConsumed): Reprocess file empties
    every field the file does not fill.
  • Own streams are rewritten only into the activity they were written to.

Details: README – Modes. Verified live on one activity: all 24 streams and 21 Garmin fields from the original, training load unchanged, and all of it still there after Reprocess file.

Install: pip install -U garmin-intervals-bridge (Windows: py -m pip install --upgrade garmin-intervals-bridge), or the image ghcr.io/futureweb/garmin-intervals-bridge:0.9.0 / futurewebat/garmin-intervals-bridge:0.9.0 (Docker Hub).