Push It: Meine eigene Fitness-App für die Bangle.js

März 25, 2026·
David Bösiger
David Bösiger
· 6 Min Lesezeit
blog

In einem früheren Beitrag habe ich darüber geschrieben, warum ich die Bangle.js 2 als Smartwatch gewählt habe - wegen der Datenhoheit. Ich habe einen Folgebeitrag versprochen, wie ich meine eigene Fitness-App gebaut habe. Hier ist er.

Wie es begann

Ich machte Freeletics und war motiviert. Dann habe ich mir ein günstiges Gym-Abo für einen Monat geholt. Problem: Freeletics trackt Übungen mit Gewichten nicht wirklich. Ich brauchte etwas anderes.

Ich probierte wger, einen Open-Source Workout-Tracker. Dort begann ich auch mein Gewicht zu loggen. Aber wgers Mobile-UI ist klobig, und die Hälfte der Zeit findet man die Übungen nicht, die man sucht. Es funktionierte, aber es war nicht toll.

Etwa zur gleichen Zeit wollte ich mit dem Laufen anfangen. Dafür wollte ich eine Uhr, um meinen Fortschritt zu tracken. Aber ich wollte auch meine Daten besitzen - keine Garmin-Cloud, kein Strava das meine Routen besitzt. Da fand ich die Bangle.js 2.

Die Bangle.js ist sehr minimalistisch. Aber sie läuft mit JavaScript, und ich bin Entwickler. Also dachte ich: warum nicht meine eigene Plattform bauen?

Der Tech Stack

  • Laravel 12 - PHP-Backend mit Inertia.js
  • Vue 3 - Frontend mit Composition API
  • TailwindCSS - Dark Theme UI
  • PostgreSQL - Datenbank
  • Leaflet - Karten für Lauf-Visualisierung

Die App heisst Push It - weil man das macht, wenn es hart wird.

Der Bau: Zuerst die Uhr-Verbindung

Ich begann mit der Uhr-Verbindung. Die Bangle.js läuft mit JavaScript, also schrieb ich eine eigene App, die GPS-Punkte, Herzfrequenz und Schritte sammelt und dann via HTTP synchronisiert.

Geräte-Registrierung

Man registriert ein Gerät in der Web-UI, das einen 64-Zeichen API-Token generiert:

$device = $request->user()->devices()->create([
    'name' => $validated['name'],
    'api_token' => Str::random(64),
]);

Diesen Token konfiguriert man auf der Uhr. Beim ersten Sync sendet die Uhr ihre Hardware-ID, die mit dem Account verknüpft wird.

Das Sync-Protokoll

Die Uhr sendet ein JSON-Payload mit Metriken und GPS-Tracks:

{
  "device_id": "Bangle.js abc123",
  "metrics": [
    { "timestamp": 1711234567000, "hr": 72, "steps": 150 }
  ],
  "gps_tracks": [
    {
      "id": 1,
      "started": 1711234500000,
      "ended": 1711236000000,
      "points": [
        { "ts": 1711234500000, "lat": 47.123, "lon": 8.456, "alt": 450 }
      ]
    }
  ]
}

Der Server validiert alles und behandelt Duplikate indem er Timestamps vor dem Einfügen prüft. So entstehen keine Duplikate, wenn ein Sync teilweise fehlschlägt und wiederholt wird.

CI/CD von Tag Eins

Ich habe die App früh auf meinem Server veröffentlicht und GitLab CI/CD für Auto-Deployment eingerichtet. Das bedeutete, ich konnte Änderungen pushen und sofort auf meinem Handy testen. Essentiell für schnelles Iterieren.

Ich habe auch ein PWA-Manifest hinzugefügt, sodass ich Push It als App auf meinem Handy installieren konnte. Fühlt sich nativ an, startet vom Homescreen, keine Browser-UI die im Weg ist.

Das GPS-Problem

Es brauchte ein paar Versuche, bis das Tracking richtig funktionierte. Das erste Problem: zu viele GPS-Punkte. Die Uhr zeichnete Punkte zu häufig auf, und kleine GPS-Schwankungen summierten sich zu absurden Distanzen. Ein 5km-Lauf zeigte 100km an.

Die Lösung war der “Recalculate”-Button. Er berechnet die Distanz aus den GPS-Punkten mit der Haversine-Formel neu und filtert das Rauschen heraus. Später fügte ich auch korrektes distanzbasiertes Sampling für die Höhenberechnungen hinzu.

Features über die Zeit hinzufügen

Sobald das grundlegende Run-Tracking funktionierte, fügte ich weiter hinzu:

  • Karten-Visualisierung - Route auf OpenStreetMap via Leaflet
  • Höhenprofil - Diagramm das Höhenänderungen während des Laufs zeigt
  • Herzfrequenz-Graph - HRM-Daten überlagert mit der Lauf-Timeline
  • Splits - Pace pro Kilometer mit visuellen Balken

Die Herzfrequenz von der Uhr war während Läufen nicht toll - handgelenkbasiertes HRM kämpft wenn man sich bewegt. Ich kaufte schliesslich einen Polar H9 Brustgurt, der die Sache deutlich verbesserte. Die Uhr zeichnet immer noch HR auf, aber für ernsthafte Läufe nutze ich den Brustgurt.

Migration von wger

wger ging mir auf die Nerven. Das UI, die fehlenden Übungen, die allgemeine Klobigkeit. Zeit, alles nach Push It zu migrieren.

Zuerst fügte ich Gewichtstracking hinzu. Einfaches Feature - logge dein Gewicht täglich, sieh ein Diagramm über die Zeit. Das mache ich immer noch jeden Morgen. Dann schrieb ich ein Migrations-Skript um meine ganze Gewichtshistorie aus wger zu importieren. Keine Daten zurückgelassen.

Danach baute ich das Routine-System.

Routinen erstellen

Eine Routine ist eine Sequenz von Übungen mit:

  • Sets und Reps (oder Dauer für zeitbasierte Übungen wie Planks)
  • Gewicht (für Gym-Übungen)
  • Pausenzeiten zwischen Sets
  • Übungsbilder/GIFs als Referenz
  • Zweiseitig-Flag (für Übungen wie Side Planks)

Der Player

Das Killer-Feature ist der Workout-Player - führe Routinen aus wie bei Freeletics:

  1. Übungsanzeige - Aktuelle Übung mit Bild, Reps/Dauer und Gewicht
  2. Vorbereitungs-Countdown - 5 Sekunden mit Beeps vor zeitbasierten Übungen
  3. Timer - Für dauerbasierte Übungen, mit Countdown für die letzten 5 Sekunden
  4. Pausen-Timer - Kreisförmiger Fortschritt zwischen Sets
  5. Audio-Signale - Beeps, Abschluss-Sounds, sogar Sprach-Countdown

Das Interface ist fürs Gym designed - grosse Buttons, klare Typografie, funktioniert wenn man schwitzt.

Spontan anpassen

Während eines Workouts kannst du:

  • Reps/Gewicht/Dauer für das aktuelle Set anpassen
  • Extra Sets hinzufügen wenn du dich stark fühlst
  • Sets oder Übungen überspringen wenn etwas weh tut
  • Änderungen in die Routine zurückspeichern

Das letzte ist der Schlüssel. Erhöhe das Gewicht beim Bankdrücken, tippe “Save to Routine”, und nächste Woche startet mit diesem Gewicht.

Fortsetzen-Feature

Wenn du mitten im Workout abbrichst, wird der Fortschritt im localStorage gespeichert. Komm innerhalb einer Stunde zurück und du kannst genau dort weitermachen wo du aufgehört hast.

Stück für Stück

Der Player kam nicht mit all diesen Features. Ich baute es Stück für Stück:

  • Erste Version: zeigte nur die Übung und einen “Done”-Button
  • Dann fügte ich den Timer für zeitbasierte Übungen hinzu
  • Dann Pausenzeiten zwischen Sets
  • Dann Audio-Feedback
  • Dann die Anpassungs-Controls
  • Dann das Fortsetzen-Feature

Jede Ergänzung kam, als ich sie während eines Workouts tatsächlich brauchte. Jetzt fühlt es sich ziemlich solide an.

Was kommt als Nächstes: Machine Learning

Die Daten sammeln sich an. Jedes Workout, jeder Lauf, jede Herzfrequenz-Messung. Das eröffnet Möglichkeiten:

  • Fortschritts-Vorhersage - Wann erreiche ich meinen nächsten PR?
  • Ermüdungs-Erkennung - HR-Muster die darauf hindeuten, dass ich Ruhe brauche
  • Routine-Optimierung - Welche Kombinationen führen zu den besten Gains?
  • Anomalie-Erkennung - Warnung wenn etwas komisch aussieht

Die Rohdaten unter meiner Kontrolle zu haben macht das möglich. Mit Cloud-Services bist du limitiert auf die Analytics, die sie dir zeigen.

Lektionen gelernt

1. Starte mit dem was du brauchst. Uhr-Verbindung zuerst, weil das das Kernproblem war. Routinen kamen später als wger mich genug frustrierte.

2. Iteriere in Produktion. CI/CD von Tag eins bedeutete, dass ich echte Workouts sofort testen konnte. Fand Probleme so schneller.

3. Baue für dich selbst. Ich versuche nicht mit Strava oder Freeletics zu konkurrieren. Ich brauchte einfach etwas das für mich funktioniert. Diese Freiheit, eigenwillig zu sein, machte die Entwicklung schneller.


In zukünftigen Beiträgen: Höhenkorrektur mit DEM-Daten, Grade Adjusted Pace für hügelige Läufe, und das Stats-Dashboard.