Blog
Wo Coding auf Sport trifft: Ich baue die Lauf-App meiner Träume
Bas van de Sande

"WAS!? SIE MACHEN WOHL WITZE! AUF KEINEN FALL MUSS ICH FÜR DIE NUTZUNG MEINER EIGENEN DATEN BEZAHLEN? DIESE COMPANION APP IST NUTZLOS!"
Als ich die ausgegraute Funktion in der Begleit-App meines Laufbands sah, war ich wütend. Nachdem ich meine persönlichen Laufdaten in die Begleit-App hochgeladen hatte, stellte sich heraus, dass ich für die Nutzung meiner eigenen Daten bezahlen musste. Das teure High-End-Laufband, das ich gekauft hatte, war im Grunde genommen wertlos für mein Trainingsziel: das Training für das Laufen in den Bergen. Ich schaue immer noch ungläubig auf den Bildschirm in der App.
Nachdem ich monatelang gezögert hatte, ob ich die Investition tätigen sollte oder nicht, bekam ich schließlich grünes Licht von meiner besseren Hälfte. Nun konnte ich das Laufband nicht wie vorgesehen nutzen. Wie sollte ich ihr erklären, dass das Laufband meine Trainingsbedürfnisse nicht erfüllte? In diesem Moment hatte ich einen kurzen Funken von Kreativität. In meinem Kopf entstand diese kühne Idee: "Warum nicht meine eigene App entwickeln? Die Lauf-App meiner Träume. Eine App für Läufer, entwickelt von einem Läufer!" Ein Vorhaben, das ziemlich schnell eskalierte, und schon bald gingen meine Ideen für die App über meine Träume hinaus, weit über meine Träume hinaus...
Die Idee hinter der App
Die Idee für die Lauf-App entstand aus der Möglichkeit, Strecken zu laufen, die ich zuvor gelaufen bin, Läufe, die von meiner Sportuhr aufgezeichnet wurden. Die App sollte in der Lage sein, beim Laufen auf dem Laufband die Strecke mit ihren Anstiegen und Abfahrten zu simulieren, so dass ich das Klettern für den nächsten Berglauf üben kann.
Wo soll ich anfangen?
Ganz gleich, wie groß oder klein ein Projekt ist, es gibt immer eine Liste von Problemen, die Sie überwinden müssen. In diesem Fall war es nicht anders. Bevor ich in die Falle tappte, ein komplettes Backlog zu erstellen, schrieb ich eine kleine Liste mit den Dingen auf, die mir in den Sinn kamen. Anhand dieser Liste konnte ich leicht neue Schritte hinzufügen, Ideen entfernen und Aufgaben als erledigt markieren. Nichts Ausgefallenes, keine Prioritäten, nur eine Liste von Dingen, die ich angehen musste. Oldskool-Projektmanagement.
Zunächst sah ich drei Hauptprobleme, die es zu lösen galt: - Kontrolle über das Laufband (z. B. Steuerung der Steigung und Geschwindigkeit, Ablesen von Statistiken wie Geschwindigkeit und Entfernung), - Arbeit mit GPS-Daten, um die Strecke zu erstellen, die ich laufen möchte, - Zeichnen eines schönen Höhendiagramms, um eine Visualisierung der zu laufenden Strecke zu erhalten.
Oh... und die App sollte plattformübergreifend sein (Windows, Android usw.), denn ich möchte sie auf einem Tablet laufen lassen, das ich auf mein Laufband stellen kann, mit Integration von Strava usw.
Das Wichtigste zuerst...
Um herauszufinden, wie man das Laufband steuert, habe ich den Hersteller kontaktiert und um Dokumentation über das Protokoll gebeten, das über die integrierte Bluetooth-Verbindung gesendet wird. Der Hersteller wollte die Dokumentation nicht zur Verfügung stellen und sagte, es handele sich um vertrauliche F&E-Informationen. Ich sagte ihm, dass ich das mit Hilfe von Reverse Engineering selbst herausfinden würde. Nach einigen Recherchen fand ich ein von Nordic Semiconductor entwickeltes Tool namens nRF Connect. Dabei handelt es sich um ein Low-Level-Analysetool, mit dem die Kommunikation von Bluetooth-fähigen Geräten analysiert werden kann.
Nachdem ich mit dem Scannen begonnen hatte, stellte ich fest, dass das Laufband Bluetooth LE verwendet und dass sich das Gerät über das FITTnes Machine Service (FTMS) Protokoll als Fitnessgerät identifiziert. Dies ist ein standardisiertes Bluetooth-Protokoll, dessen Spezifikationen auf der Website bluetooth.com öffentlich zugänglich sind.
Nachdem ich erfahren hatte, dass das Laufband Bluetooth LE und die FTMS-Spezifikation verwendet, musste ich herausfinden, wie man über das Bluetooth LE-Protokoll kommuniziert. An diesem Punkt beschloss ich, GitHub Copilot mit der folgenden Eingabeaufforderung um Rat zu fragen.
I want to build an app using C# that is cross platform. The app needs to be able to communicate over bluetooth LE to a treadmill. How do I start?
Die Antwort war ein Überblick über die Schritte, die ich tun musste, um loszulegen, mit einem kleinen Beispiel, wie man Bluetooth-Geräte mit dem InTheHand.Bluetooth NuGet-Paket erkennt.
Ich habe das Beispiel in einer kleinen Konsolenanwendung implementiert und es ausprobiert. Mein Laufband tauchte in der Liste der verfügbaren Geräte auf und ich konnte eine Verbindung dazu herstellen. Die erste Hürde war genommen. GitHub Copilot wurde von diesem Moment an ein geschätztes Teammitglied in meinem Projekt. Ich begann, ihn zu bitten, mir alles über das FTMS 1.0 Protokoll und dessen Verwendung zu erklären. Und ehe ich mich versah, lernte ich alles über die Verwendung von Bluetooh GATT-Servern, Kontrollpunkte (Eingangspunkt zum Senden von Befehlen), Merkmale (Empfänger von Informationen) und die standardisierten dokumentierten Guids, die sie verwendeten. Bald war ich in der Lage, meine eigene Steuerklasse zu erstellen, mit der ich die von mir benötigten Aspekte des Laufbands steuern konnte: Starting it, Stopping it, Changing the Inclination, Changing the Speed, Retrieving device characteristis (such as speed range, inclination range).
Normalerweise müssen Sie ein Bluetooth-Gerät erst koppeln, bevor Sie es verwenden können. Aufgrund der Beschaffenheit eines Fitnessgeräts - ein Gerät, das von mehreren Personen verwendet wird - erlaubt es keine Kopplung. Stattdessen müssen Sie sich jedes Mal, wenn Sie es verwenden möchten, mit ihm verbinden. Das bedeutet im Grunde genommen, dass Sie jedes Mal eine Erkennung durchführen, das Gerät aus einer Liste verfügbarer Geräte auswählen, die Verbindung herstellen müssen usw. Ein zeitaufwändiger Prozess. Glücklicherweise kann dieser Prozess beschleunigt werden. Es hat sich herausgestellt, dass Sie nach einer ersten Erkennung des Geräts dessen eindeutige ID speichern können. Sie können diese Kennung verwenden, um nur Geräte mit dieser Kennung und vom Typ "Fitnessgerät" zu finden. Auf diese Weise können Sie den Handshake durchführen und die Verbindung im Hintergrund herstellen, ohne den Benutzer zu stören.
public static async Task<BluetoothDevice?> GetOrRequestDeviceAsync(string deviceIdFile, Guid optionalService , bool showDialog=true)
{
BluetoothDevice? device;
if (File.Exists(deviceIdFile))
{
// Reuse device
string deviceId = File.ReadAllText(deviceIdFile);
device = await BluetoothDevice.FromIdAsync(deviceId);
if (device != null)
{
if (!device.Gatt.IsConnected) await device.Gatt.ConnectAsync();
return device;
}
}
if (!showDialog) return null;
// Scan for new device
device = await Bluetooth.RequestDeviceAsync(new RequestDeviceOptions
{
AcceptAllDevices = true,
OptionalServices = { optionalService } // Service UUID
});
if (device != null)
{
File.WriteAllText(deviceIdFile, device.Id);
if (!device.Gatt.IsConnected) await device.Gatt.ConnectAsync();
return device;
}
return null;
}
Ich habe das gleiche Prinzip für den Anschluss eines drahtlosen Herzfrequenzsensors angewendet. Auch diese Sensoren haben ihre eigene Spezifikation, die in der Heartrate Service 1.0-Spezifikation beschrieben wird, die Sie ebenfalls auf der Website bluetooth.com finden können.
Die nächste Herausforderung: GPS-Daten
Wenn Sie im Freien laufen, laufen Sie eine Strecke, die in jede Richtung führt. Auf einem Laufband können Sie nur in die Vorwärtsrichtung laufen. Die Idee der App war es, die Steigungen und Gefälle zu simulieren, denen Sie beim Laufen auf einer Strecke begegnen. Das bedeutet, dass eine echte 3D-GPS-Route in eine 2D-Strecke umgewandelt werden sollte, bei der es zwei Dimensionen gibt: Entfernung und Höhenunterschied.
Das GPX-Datenformat ist ein standardisiertes Format. Ein Format, in dem Punkte jedes Mal aufgezeichnet werden, wenn sich Richtung oder Höhe ändern. Um die Entfernung zwischen zwei GPS-Punkten (Koordinaten) zu berechnen, kann die Haversine-Entfernungsformel verwendet werden. Die Haversinus-Formel berechnet die kürzeste Entfernung zwischen zwei Punkten auf einer Kugel. Ich habe GitHub Copilot gebeten, eine Haversine-Implementierung in C# zu erstellen. Durch die Verwendung dieser Formel bin ich nicht auf ein externes GPS-Paket angewiesen (je weniger Abhängigkeiten, desto besser).
private static decimal GetDistance(double lat1, double lon1, double lat2, double lon2)
{
// Radius of the Earth in meters
double R = 6371000;
double dLat = ToRadians(lat2 - lat1);
double dLon = ToRadians(lon2 - lon1);
// intermediate geometric calculation
double a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
Math.Cos(ToRadians(lat1)) * Math.Cos(ToRadians(lat2)) *
Math.Sin(dLon / 2) * Math.Sin(dLon / 2);
// angle between the two points as seen from Earth's center
double c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
// actual distance on Earth's surface
double distance = R * c;
return (decimal)distance;
}
Jetzt, da ich die Entfernung zwischen den GPS-Punkten kenne, habe ich die Grundlagen, die ich für das Laufband brauche. Anhand der Entfernung im Track können die Anstiege simuliert werden. Das bringt mich zu dem folgenden Problem. An einigen Punkten der Strecke kann der Abstand zwischen den GPS-Punkten sehr kurz sein, eigentlich zu kurz, um das Laufband richtig reagieren zu lassen. Es dauert einige Sekunden, um die Neigung oder die Geschwindigkeit des Laufbands zu ändern, was sinnvoll ist, denn als Benutzer könnten Sie katapultiert werden. Um dieses Problem zu lösen, habe ich beschlossen, Abschnitte von etwa 100 Metern einzuführen. Auf der Grundlage der berechneten Meter Steigung und Gefälle in diesem Segment wird der durchschnittliche Neigungsprozentsatz berechnet. So kann das Laufband kontrolliert und sicher auf Geschwindigkeits- und Höhenänderungen reagieren.
Angetrieben von Ereignissen und Daten
Die Idee war, einen Motor (Player) zu haben, der über zwei Achsen gesteuert wird: Daten, die die Strecke darstellen, und kontinuierliches Feedback vom Laufband.
Der Player nimmt die GPS-Daten, wandelt sie in 100-Meter-Segmente um und beginnt, die Segmente nacheinander abzuspielen. Vom Laufband wird ständig ein Strom von Ereignissen empfangen, z.B. Informationen über die Geschwindigkeit, die Entfernung, die Neigung usw. Anhand dieser Ereignisinformationen weiß die Engine, wann das nächste Segment abgespielt werden sollte und welche Befehle an das Laufband gesendet werden müssen, um die Geschwindigkeit oder Neigung zu ändern. Sobald alle Segmente abgespielt wurden, hält das Laufband an. Ein einfaches, aber solides Konzept.
Zu diesem Zeitpunkt hatte ich eine konsolenbasierte Anwendung, mit der ich ein Laufband auf der Grundlage einer GPX-Datei steuern konnte.
Auf der Konsole wird ein START Befehl an den Player gesendet. Der Player lädt die gpx-Datei, wandelt sie in einen Track um und startet den Antriebsstrang des Laufbands. Das Laufband registriert die Entfernung und sendet einen Ereignisstrom mit Metriken (wie Geschwindigkeit, Entfernung, Höhe usw.) zurück. Der Player wandelt den Ereignisstrom in für den Menschen lesbare Daten um und sendet einen Ereignisstrom an die Konsolen-App zurück, wo er auf der Konsole ausgegeben wird.
Der Player verwendet die Metriken zur Steuerung des Laufbands. Anhand der aktuellen Entfernung weiß der Spieler, ob das Laufband aufsteigen, absteigen, beschleunigen, abbremsen oder anhalten soll. Im Grunde eine endlose ereignisgesteuerte Schleife, die abbricht, sobald alle Segmente abgespielt wurden.
Ich möchte Augenschmaus
Nach wochenlangen Überlegungen war es an der Zeit, mit der Arbeit an einer visuell ansprechenden Benutzeroberfläche zu beginnen. Während meiner wöchentlichen Langstreckenläufe begannen die Ideen Gestalt anzunehmen und die Liste der Dinge, die zu tun waren, wuchs in rasantem Tempo. Ideen wie ein automatischer Tempomat, um die Geschwindigkeit des Laufbands während der Steigungen anzupassen, eine Höhentabelle, in der ich sehen konnte, welchen Teil ich noch laufen und klettern musste, Statistiken, der Fluss der Seiten in der App. Ich habe darüber nachgedacht, welche Technologie/welches Framework ich für die App verwenden sollte und so weiter.
Ich stellte die Frage nach der Technologie/dem Framework an GitHub Copilot und wurde bald darauf davon überzeugt, dass MAUI der richtige Weg für mich ist. Da ich noch keine Erfahrung mit MAUI hatte, stellte ich Copilot die folgende Frage:
I want to create the app in MAUI and I need the following flow of screens. From the title page, I want to navigate to a route selection screen. Once I select a route, I want to navigate to a detail screen in which I can see some statistics of the route. Once I press next button, I must navigate to the activity screen. The activity screen has a finish button that leads to a summary screen. The summary screen has an exit button that will navigate to the title screen. How do I do this?
Diese knappe Aufforderung führte zu einer Skelton, die sowohl die XAML als auch den Code enthielt, den ich implementieren musste. Von diesem Zeitpunkt an konzentrierte ich mich auf die Arbeit an den einzelnen Bildschirmen. Die Funktionalität war mein Hauptanliegen, Layout und Optik mein zweites. Dann tauchte die nächste Herausforderung auf: Ich brauchte ein Höhendiagramm der Route, die ich fahren wollte. Neun Eingabeaufforderungen später war die Grundlage für die Höhentabelle geschaffen. Ich dokumentierte den Prompting-Dialog in meinem persönlichen Blog (https://azurecodingarchitect.com/posts/artofconversation/). Da ich nun wusste, wie man Diagramme zeichnet, war es nur noch eine Frage der Technik, einen soliden Graphikplotter zu schreiben, der die GPS-Daten in ein Diagramm umwandelt.
Als die Zeit voranschritt und ich mehr Zeit auf dem Laufband verbrachte, wurde mir klar, dass das Ziel der App darin bestehen sollte, die Läufe, die ich früher gelaufen bin, wieder zu erleben. Nur eine langweilige Höhentabelle und eine Herzfrequenzanzeige zu haben, würde nicht ausreichen. Ich musste auf jeden Fall mein Spiel verbessern, indem ich mehr Eyecandy ins Spiel brachte. Bei einem meiner Läufe im Freien überlegte ich, eine Diashow mit Bildern zu erstellen, die ich bei früheren Läufen gemacht hatte, aber diese Idee wurde schnell wieder verworfen. Kurz darauf kam mir eine sehr kühne Idee: IMMERSIVE VIDEOS!
Ich will Video! Wie cool wäre es, Ihre Läufe zu filmen und sie dann im Hintergrund abzuspielen, mit einer Höhentabelle darunter und Statistiken darüber? Bald darauf kaufte ich eine GoPro und etwas Zubehör und begann, POV-Aufnahmen beim Laufen zu machen.
Copilot hat mir gezeigt, dass es ein sehr solides, plattformübergreifendes MediaToolkit-Paket gibt, das ich verwenden kann. Open-Source, entwickelt von der .NET-Community - unterstützt von Microsoft - und in der Lage, MP4-Videos zu verarbeiten. Das Einzige, was ich tun musste, war, das Video dem Steuerelement zuzuweisen und die Funktion Play() aufzurufen. Dies funktionierte teilweise. Immer wenn ich meine Geschwindigkeit ändere, indem ich beschleunige oder verlangsame, sollte das Video darauf reagieren; mit anderen Worten, das Video sollte die Wiedergabe stoppen, wenn ich das Ende des Tracks erreiche. Ich habe dieses Problem gelöst, indem ich den folgenden Code implementiert habe, der die Abspielgeschwindigkeit des Videos an die aktuelle Geschwindigkeit anpasst.
private void UpdateRouteVideoSpeed()
{
if (_hasVideo && RouteVideo.Duration.TotalSeconds > 0)
{
double secondsToGo = CalculateRemainingSeconds();
double remainingVideoSeconds = RouteVideo.Duration.TotalSeconds - RouteVideo.Position.TotalSeconds;
// If the video is not playing, we set the speed to 0 (same applies for the first segment)
if (!_isFirstSegment)
RouteVideo.Speed = (secondsToGo > 0) ? (remainingVideoSeconds / secondsToGo) : 0;
}
}
private double CalculateRemainingSeconds()
{
decimal? totalDistanceM = _gpxProcessor.TotalDistanceInMeters;
decimal? distanceCoveredM = _playerStatistics.CurrentDistanceM;
decimal? currentSpeedKmh = _playerStatistics.CurrentSpeedKMH;
// Convert speed from km/h to m/s
double currentSpeedMps = currentSpeedKmh.HasValue ? (double)currentSpeedKmh.Value / 3.6 : 0;
// Remaining distance
double remainingDistanceM = (double)((totalDistanceM ?? 0) - (distanceCoveredM ?? 0));
// Remaining time in seconds
double remainingTimeSeconds = (currentSpeedMps > 0)
? Math.Max(0, remainingDistanceM / currentSpeedMps)
: double.PositiveInfinity;
return remainingTimeSeconds;
}
In diesem Code berechne ich auf der Grundlage meiner aktuellen Geschwindigkeit, wie lange es dauert, bis ich am Ende der Strecke ankomme. Für das Video tue ich im Grunde dasselbe: Ich stelle fest, wie viele Sekunden im Video noch übrig sind. Ich passe die Abspielgeschwindigkeit des Videos an, indem ich die verbleibenden Sekunden im Video durch die Anzahl der Sekunden teile, die ich noch laufen muss, bevor ich am Ende der Strecke ankomme. Jedes Mal, wenn die Laufbandklasse eine Geschwindigkeitsänderung feststellt, wird ein Ereignis ausgelöst, auf das der Videoplayer reagieren kann. Dieser Mechanismus sorgt dafür, dass das Video mehr oder weniger mit der Position im Lauf synchronisiert ist. Aus Sicht des Benutzers ist es einfach, der App eine persönliche Aufnahme hinzuzufügen. Nehmen Sie eine Videoaufnahme, die die zuvor gelaufene Strecke abdeckt, konvertieren Sie sie in ein MP4-Format, benennen Sie sie genauso wie die GPX-Datei und legen Sie sie in denselben Ordner. Unabhängig von der Anzahl der Bilder pro Sekunde oder der Auflösung wird das Video wie erwartet skaliert und abgespielt.
Hilfe, meine App friert ein!
Ich habe die App veröffentlicht und auf einem ausrangierten Surface Go 3 Tablet (Pentium Gold CPU, 4GB RAM, 64GB eMMC Speicher) installiert. Ich habe es gestartet und mit dem Laufband verbunden. Ich begann zu laufen, mein zuvor aufgenommenes Video wurde abgespielt, und das Laufband reagierte entsprechend. Nach ein paar Minuten bemerkte ich ein Einfrieren, und kurz darauf kam die App zum Stillstand. Was war passiert? Um der Ursache dieses Problems auf den Grund zu gehen, stellte ich fest, dass es nicht günstig ist, eine App zu debuggen, während sie mit einem Laufband verbunden ist. Ich beschloss, einen Laufband- und Herzfrequenzsimulator zu implementieren, der sich wie die echte Hardware verhält.
using System.Timers;
namespace Re_RunApp.Core;
public class HeartRateSimulator : IHeartRate
{
public event Action<int>? OnHeartPulse;
public bool Enabled { get; set; } = true;
public int CurrentRate { get; private set; } = 70;
private System.Timers.Timer? _timer; // Fully qualify the Timer type
private readonly Random _random = new();
public Task<bool> ConnectToDevice(bool showDialog = true)
{
// every 5 seconds generate the current heart reate
_timer = new System.Timers.Timer(5000);
_timer.Elapsed += (s, e) =>
{
// Simulate heart rate between 123 and 140 bpm
CurrentRate = _random.Next(123, 140);
OnHeartPulse?.Invoke(CurrentRate);
};
_timer.Start();
return Task.FromResult(true);
}
public void Disconnect()
{
_timer?.Stop();
_timer?.Dispose();
_timer = null;
}
}
Die Simulatoren erzeugen mit der gleichen Geschwindigkeit wie das Laufband und der Herzfrequenzsensor Ereignisströme - sie simulieren die eingehenden Gerätedaten, wenn der Läufer läuft. So konnte ich testen, warum die App mit der Zeit ins Stocken geriet. Es stellte sich heraus, dass meine Laufband-/Herzfrequenzsensorklasse für jedes Ereignis, das vom tatsächlichen Laufband oder Herzfrequenzsensor empfangen wurde, ein neues Ereignis aufrief, das an die Spielerklasse weitergegeben wurde.
Die Playerklasse rief dann ein Ereignis auf, das an den Hauptthread der Aktivitätsseite weitergegeben wurde. Ohne es im Voraus zu wissen, habe ich eine massive Ereignisüberlastung des Haupt-UI-Threads der Seite verursacht. Um dieses Problem zu lösen, musste ich einen Drosselungsmechanismus implementieren, der ein Ereignis erst dann an die Aktivitätsseite weitergibt, wenn ein bestimmtes Intervall abgelaufen ist.
Der folgende Mechanismus zur Drosselung von Ereignissen wurde in der Player-Klasse implementiert, so dass ich die Anzahl der ausgelösten Ereignisse steuern kann. Wenn die Ereignisse des Herzfrequenzsensors empfangen werden, werden sie gerade aufgezeichnet. Sobald das Laufband sein Ereignis sendet, werden auch die Herzfrequenzdaten in demselben Ereignis weitergegeben:
private void HeartRate_OnHeartPulse(int heartRate)
{
if (_heartRate.Enabled == false) return;
_playerStatistics.CurrentHeartRate = heartRate; // Just register the current value...
}
private void Treadmill_OnStatisticsUpdate(TreadmillStatistics e)
{
var now = DateTime.UtcNow;
if (_firstStatisticsUpdate || (now - _lastStatisticsUpdate) < StatisticsUpdateInterval)
{
_firstStatisticsUpdate = false;
return; // Ignore events that come in too quickly, except the start :)
}
_lastStatisticsUpdate = now;
if (_isPlaying)
{
_totalDistanceM = (decimal)e.DistanceM;
_playerStatistics.CurrentDistanceM = _totalDistanceM < _playerStatistics.CurrentDistanceM ?
_playerStatistics.CurrentDistanceM + _totalDistanceM :
_totalDistanceM;
_playerStatistics.CurrentSpeedKMH = e.SpeedKMH;
// Forward the throttled update
OnStatisticsUpdate?.Invoke(_playerStatistics);
}
}
Zeit, es aufzupeppen
Von Zeit zu Zeit habe ich ein paar schnelle Verbesserungen vorgenommen, nur für die Moral. So habe ich zum Beispiel die Benutzeroberfläche überarbeitet, ein schöner Bereich, an dem man arbeiten kann. Ich wollte, dass die App lebendiger wird, um die Energie zu betonen, damit es Spaß macht, sie zu benutzen. Ich beschloss, einfache, subtile pulsierende Animationen wie eine Art Herzschlag einzuführen. Das Ergebnis war erstaunlich. Von da an beschloss ich, dies auch in den Laufbildschirm zu implementieren, und zwar in Form von Textmeldungen wie "Start Running", "Checkpoint", "Finish" und so weiter. Beim Laufen stellte ich fest, dass mich diese automatisch erscheinenden pulsierenden Meldungen motivierten.

Zeit, das letzte Stück zu bearbeiten...
Das Recht zu prahlen
Heutzutage trägt fast jeder seine Lauf- oder Radfahrdaten nach dem Training auf Plattformen wie Strava ein. Ich wollte auch eine solche Funktion haben, mit der ich meine Laufdaten hochladen kann, nachdem ich einen Lauf abgeschlossen habe. Das erwies sich als Herausforderung, denn ich brauchte eine ganze Weile, bis ich den Authentifizierungsprozess verstanden hatte. Der Authentifizierungsprozess basierte auf OAuth2, mit einer kleinen Abwandlung, die schlecht dokumentiert war.
Egal wie sehr ich mich bemühte, ich habe es nicht verstanden. Ich habe den Ablauf, der in der Dokumentation beschrieben wurde, nicht verstanden. Vielleicht ist es die Tatsache, dass ich visuell orientiert bin. Selbst als ich mich mit Gpt4.0 an Copilot wandte, gelang es mir nicht, einen funktionierenden Authentifizierungsablauf zu finden. Ungeachtet der verschiedenen Prompting-Techniken, die ich verwendet habe.
In der Zwischenzeit hatte ich einige Diskussionen von Kollegen über Claude gehört. Ich beschloss, das Sprachmodell umzustellen und bat um einen Authentifizierungsablauf gegen die Strava Api. Claude konnte mir erklären, wie es funktionierte und lieferte einen Code, der funktionierte, egal wie oft ich ihn gegen die API laufen ließ.
Was war die Wendung, die Strava eingebaut hat?
Auf der Strava-Website können Sie eine Anwendung registrieren, in der Sie eine Callback-URL angeben (eine nicht vorhandene reicht aus). Diese Anwendung erhält eine Client-ID, ein Client-Geheimnis und ein Aktualisierungs-Token.
Mit der Client-ID, der Callback-URL und dem gewünschten Bereich können Sie einen Autorisierungscode anfordern, indem Sie eine Autorisierungsseite aufrufen. Nach der Autorisierung wird Ihr Webclient auf die Callback-URL umgeleitet, in die der Autorisierungscode als Abfrageparameter eingebettet ist.
Wenn Sie den Code aus der URL nehmen, können Sie den Autorisierungscode in Kombination mit der Client-ID und dem Client-Geheimnis gegen ein Inhaber-Token austauschen. Damit erhalten Sie einen kurzzeitigen, einmaligen Zugriff auf die API mit den gewünschten Berechtigungen (Scope).
Mithilfe des Inhaber-Tokens werden die GPX-Informationen des Laufs auf Strava hochgeladen.
Falls ich weitere Anrufe tätigen möchte, kann ich das Refresh-Token (aus der App-Registrierung) verwenden, um ein neues Inhaber-Token anzufordern.
Schematisch sieht der Strava-API-Fluss wie folgt aus:
Was war da für mich drin?
Zweifellos eine der unterhaltsamsten Software, die ich je geschrieben habe. Die App Re-Run wurde aus Frustration geboren und wird mich in den kommenden Jahren beim Training begleiten. Mir schwebte eine einfache App vor, die am Ende völlig aus dem Ruder lief. Ich habe gelernt: Wenn Sie eine Idee haben, machen Sie sie einfach! Fangen Sie klein an, testen Sie, ob das Konzept machbar ist, und beginnen Sie von da an zu expandieren und zu experimentieren. Folgen Sie dem Prinzip: Schnell scheitern, schnell lernen! Beginnen Sie nicht mit einem großen Projektplan - das mag niemand. Notieren Sie sich einfach ein paar Anmerkungen in einer Liste - Ihrem lebenden Backlog.
Was ich dabei gelernt habe, ist, dass man sich Zeit zum Nachdenken nehmen muss, ein Luxus, den man sich bei der Arbeit an kommerziellen Projekten nicht immer leisten kann. Indem ich mich zurücklehnte, nachdachte und mich inspirieren ließ, wurde die Software so viel besser, als ich es mir je hätte wünschen können. Beim Laufen im Freien kamen mir alle möglichen wilden Ideen in den Sinn, manche gut, andere schlecht. Während ich die verschiedenen Iterationen der App mit meinem Laufband testete, fielen mir Fehler und Verbesserungen auf. Das Hinzufügen von Hardware-Simulatoren für das Laufband und den Herzfrequenzsensor hat mir geholfen, Funktionen zu schreiben und auf sehr effiziente Weise zu debuggen. Das ist etwas, das ich empfehlen kann; die Simulation hat den Entwicklungsprozess sehr beschleunigt.
Ich habe mich nicht darauf konzentriert, diese ausgefallene Software-Architektur einzurichten. Mein Ziel war es, den Code so einfach und sauber wie möglich zu halten, indem ich einige Prinzipien der sauberen Architektur anwandte, um ihn so einfach und wartbar wie möglich zu halten.
GitHub Copilot hat sich als unverzichtbar erwiesen. Mit seinen präzisen Hinweisen hat er mir geholfen, Code zu erstellen und logische Fehler zu beheben. Letztendlich vertraue ich ihm nicht blind, sondern vertraue auf meine eigenen technischen Fähigkeiten. Nichtsdestotrotz war die Unterstützung, die ich bei der Erkundung neuer Themen und Technologien erhielt, erstaunlich. Was die tatsächlichen Stunden angeht, die ich mit dem Schreiben von Code verbracht habe, so habe ich etwa 100-150 Stunden damit verbracht. Ein großer Teil der Zeit ging in die Verbesserung und den Feinschliff der Benutzeroberfläche (und die vielen wilden Ideen, die daraus entstanden sind).
Wenn Sie ein Bluetooth-fähiges Laufband haben und es ausprobieren möchten, können Sie mein Repository unter (https://github.com/basvandesande/Re-RunApp) klonen oder die Re-Run Website unter (https://runningapps.eu) besuchen.
Verfasst von

Bas van de Sande
Azure Coding Architect | Consultant | Integrator | Trainer A person who never made a mistake never tried anything new. – Albert Einstein
Contact




