├── .gitignore ├── .idea ├── .gitignore ├── vcs.xml ├── jpa-buddy.xml ├── ClojureProjectResolveSettings.xml ├── compiler.xml ├── misc.xml └── encodings.xml ├── app └── src │ ├── main │ ├── resources │ │ ├── MainClass.txt │ │ ├── GPX.png │ │ ├── ical.png │ │ ├── GPX128.png │ │ ├── garmin.jpg │ │ ├── garmin.pxm │ │ ├── ical128.png │ │ ├── garmin128.png │ │ ├── garmintools.png │ │ ├── instagram-32.png │ │ ├── GarminWorkoutCreator.png │ │ ├── GarminTools.desktop │ │ ├── control │ │ ├── logback.xml │ │ └── javafx │ │ │ └── main-form.fxml │ ├── logo │ │ ├── windows │ │ │ ├── duke.ico │ │ │ ├── cf-lg_312.jpg │ │ │ └── GarminTools.ico │ │ └── macosx │ │ │ └── GarminTools.icns │ └── java │ │ ├── com │ │ └── johnpickup │ │ │ └── app │ │ │ ├── calendar │ │ │ ├── ScheduleWriter.java │ │ │ ├── Programme.java │ │ │ ├── Scheduler.java │ │ │ ├── ScheduleGenerator.java │ │ │ ├── ScheduleConverter.java │ │ │ ├── Schedule.java │ │ │ ├── WorkoutScheduleConverter.java │ │ │ ├── PlannedWorkout.java │ │ │ ├── ScheduledWorkout.java │ │ │ ├── TrainingCalendarExport.java │ │ │ ├── ProgrammeWriter.java │ │ │ ├── ScheduleIcalWriter.java │ │ │ ├── ProgrammeReader.java │ │ │ └── ScheduleExcelWriter.java │ │ │ ├── converter │ │ │ ├── StepConverter.java │ │ │ ├── TimeConverter.java │ │ │ ├── PaceConverter.java │ │ │ ├── PowerConverter.java │ │ │ ├── HeartRateConverter.java │ │ │ ├── ZonePowerConverter.java │ │ │ ├── ZoneHeartRateConverter.java │ │ │ ├── OpenStepConverter.java │ │ │ ├── StepIntensityConverter.java │ │ │ ├── MaximumPaceConverter.java │ │ │ ├── MinimumPaceConverter.java │ │ │ ├── PaceRangeConverter.java │ │ │ ├── DiatanceUnitConverter.java │ │ │ ├── TimeStepConverter.java │ │ │ ├── CustomPowerConverter.java │ │ │ ├── OpenPowerStepConverter.java │ │ │ ├── PaceUnitConverter.java │ │ │ ├── OpenPaceStepConverter.java │ │ │ ├── PaceNameConverter.java │ │ │ ├── DistanceStepConverter.java │ │ │ ├── CustomHeartRateConverter.java │ │ │ ├── TimePaceStepConverter.java │ │ │ ├── TimePowerStepConverter.java │ │ │ ├── OpenHeartRateStepConverter.java │ │ │ ├── RepeatingStepsConverter.java │ │ │ ├── PowerConverterFactory.java │ │ │ ├── DistancePowerStepConverter.java │ │ │ ├── DistancePaceStepConverter.java │ │ │ ├── TimeHeartRateStepConverter.java │ │ │ ├── HeartRateConverterFactory.java │ │ │ ├── PaceConverterFactory.java │ │ │ ├── DistanceHeartRateStepConverter.java │ │ │ └── StepConverterFactory.java │ │ │ ├── task │ │ │ ├── UiTask.java │ │ │ ├── ScheduleFitTask.java │ │ │ ├── ScheduleIcalTask.java │ │ │ └── GpxTask.java │ │ │ ├── garmin │ │ │ ├── fit │ │ │ │ ├── FitGenerator.java │ │ │ │ ├── FitSaver.java │ │ │ │ └── FitReader.java │ │ │ ├── WorkoutSaver.java │ │ │ ├── workout │ │ │ │ ├── WorkoutStep.java │ │ │ │ ├── OpenWorkoutStep.java │ │ │ │ ├── TimeWorkoutStep.java │ │ │ │ ├── DistanceWorkoutStep.java │ │ │ │ ├── OpenPaceWorkoutStep.java │ │ │ │ ├── OpenPowerWorkoutStep.java │ │ │ │ ├── OpenHeartRateWorkoutStep.java │ │ │ │ ├── TimePaceWorkoutStep.java │ │ │ │ ├── TimePowerWorkoutStep.java │ │ │ │ ├── DistancePaceWorkoutStep.java │ │ │ │ ├── TimeHeartRateWorkoutStep.java │ │ │ │ ├── DistancePowerWorkoutStep.java │ │ │ │ └── DistanceHeartRateWorkoutStep.java │ │ │ ├── schedule │ │ │ │ └── ScheduledWorkout.java │ │ │ ├── route │ │ │ │ └── CoursePoint.java │ │ │ └── converter │ │ │ │ └── CourseConverter.java │ │ │ ├── javafx │ │ │ ├── AppLauncher.java │ │ │ ├── UiAppender.java │ │ │ ├── MainForm.java │ │ │ ├── ConversionType.java │ │ │ └── TaskArguments.java │ │ │ ├── gpx │ │ │ └── GpxReader.java │ │ │ ├── util │ │ │ └── Haversine.java │ │ │ ├── parser │ │ │ ├── PaceTextParser.java │ │ │ ├── TimeTextParser.java │ │ │ ├── PaceRangeTextParser.java │ │ │ └── WorkoutTextParser.java │ │ │ ├── excel │ │ │ ├── ExcelUtils.java │ │ │ └── PaceSheetReader.java │ │ │ ├── CalendarScheduleGenerator.java │ │ │ └── GarminRouteGenerator.java │ │ └── module-info.java │ └── test │ ├── resources │ ├── ExampleWorkoutSchedule.xls │ ├── MultiSportWorkoutSchedule.xls │ └── com.johnpickup.gpx │ │ ├── SimpleTest.fit │ │ ├── BedgeburyBlue.fit │ │ └── SimpleTest.gpx │ └── java │ └── com │ └── johnpickup │ └── app │ ├── parser │ ├── PaceRangeTest.java │ ├── TimeTextParserTest.java │ └── TimeTest.java │ ├── gpx │ └── GpxReaderTest.java │ └── garmin │ └── unit │ ├── DistanceTest.java │ └── PaceTest.java ├── installer └── .gitattributes ├── lib └── fit.jar ├── PowerWorkoutSchedule.xls ├── ExampleWorkoutSchedule.xls ├── MultiSportWorkoutSchedule.xls ├── common ├── src │ └── main │ │ └── java │ │ ├── module-info.java │ │ └── com │ │ └── johnpickup │ │ └── garmin │ │ └── common │ │ └── unit │ │ ├── PowerTarget.java │ │ ├── HeartRateTarget.java │ │ ├── ZonePowerTarget.java │ │ ├── PowerUnit.java │ │ ├── ZoneHeartRateTarget.java │ │ ├── HeartRateUnit.java │ │ ├── DistanceUnit.java │ │ ├── Time.java │ │ ├── PaceUnit.java │ │ ├── Power.java │ │ ├── Distance.java │ │ ├── HeartRate.java │ │ ├── CustomPowerTarget.java │ │ ├── CustomHeartRateTarget.java │ │ ├── Pace.java │ │ └── PaceTarget.java └── pom.xml ├── gpx └── src │ └── main │ ├── java │ ├── com │ │ └── johnpickup │ │ │ └── gpx │ │ │ └── PointlessExport.java │ └── module-info.java │ └── resources │ └── xjb │ └── gpx.xjb ├── parser └── src │ └── main │ └── java │ ├── com │ └── johnpickup │ │ └── garmin │ │ └── parser │ │ ├── Power.java │ │ ├── Pace.java │ │ ├── HeartRate.java │ │ ├── StepIntensity.java │ │ ├── PowerUnit.java │ │ ├── HeartRateUnit.java │ │ ├── Sport.java │ │ ├── DistanceUnit.java │ │ ├── Step.java │ │ ├── MinimumPace.java │ │ ├── MaximumPace.java │ │ ├── AntlrErrorHandler.java │ │ ├── OpenStep.java │ │ ├── PaceUnit.java │ │ ├── PaceName.java │ │ ├── PaceLimit.java │ │ ├── OpenPaceStep.java │ │ ├── Distance.java │ │ ├── OpenPowerStep.java │ │ ├── TimeStep.java │ │ ├── DistanceStep.java │ │ ├── PowerRange.java │ │ ├── OpenHeartRateStep.java │ │ ├── HeartRateRange.java │ │ ├── PaceRange.java │ │ ├── TimePowerStep.java │ │ ├── WorkoutSchedule.java │ │ ├── TimePaceStep.java │ │ ├── HeartRateZone.java │ │ ├── DistancePowerStep.java │ │ ├── DistancePaceStep.java │ │ ├── TimeHeartRateStep.java │ │ ├── PowerZone.java │ │ ├── Workout.java │ │ ├── DistanceHeartRateStep.java │ │ ├── Time.java │ │ ├── ScheduledWorkout.java │ │ └── RepeatingSteps.java │ └── module-info.java ├── ROUTES.md ├── bundle └── src │ └── assembly │ ├── macos.xml │ ├── linux.xml │ └── windows.xml ├── TODO.txt ├── README.md └── .github └── workflows ├── maven-publish-linux.yml └── maven-publish-windows.yml /.gitignore: -------------------------------------------------------------------------------- 1 | /gen/ 2 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml -------------------------------------------------------------------------------- /app/src/main/resources/MainClass.txt: -------------------------------------------------------------------------------- 1 | Main-Class: MainMenuForm -------------------------------------------------------------------------------- /installer/.gitattributes: -------------------------------------------------------------------------------- 1 | *.pkg filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /lib/fit.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpickup/GarminTools/HEAD/lib/fit.jar -------------------------------------------------------------------------------- /PowerWorkoutSchedule.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpickup/GarminTools/HEAD/PowerWorkoutSchedule.xls -------------------------------------------------------------------------------- /ExampleWorkoutSchedule.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpickup/GarminTools/HEAD/ExampleWorkoutSchedule.xls -------------------------------------------------------------------------------- /MultiSportWorkoutSchedule.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpickup/GarminTools/HEAD/MultiSportWorkoutSchedule.xls -------------------------------------------------------------------------------- /app/src/main/resources/GPX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpickup/GarminTools/HEAD/app/src/main/resources/GPX.png -------------------------------------------------------------------------------- /app/src/main/resources/ical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpickup/GarminTools/HEAD/app/src/main/resources/ical.png -------------------------------------------------------------------------------- /app/src/main/logo/windows/duke.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpickup/GarminTools/HEAD/app/src/main/logo/windows/duke.ico -------------------------------------------------------------------------------- /app/src/main/resources/GPX128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpickup/GarminTools/HEAD/app/src/main/resources/GPX128.png -------------------------------------------------------------------------------- /app/src/main/resources/garmin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpickup/GarminTools/HEAD/app/src/main/resources/garmin.jpg -------------------------------------------------------------------------------- /app/src/main/resources/garmin.pxm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpickup/GarminTools/HEAD/app/src/main/resources/garmin.pxm -------------------------------------------------------------------------------- /app/src/main/resources/ical128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpickup/GarminTools/HEAD/app/src/main/resources/ical128.png -------------------------------------------------------------------------------- /app/src/main/resources/garmin128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpickup/GarminTools/HEAD/app/src/main/resources/garmin128.png -------------------------------------------------------------------------------- /common/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module com.johnpickup.common { 2 | exports com.johnpickup.garmin.common.unit; 3 | } -------------------------------------------------------------------------------- /app/src/main/logo/windows/cf-lg_312.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpickup/GarminTools/HEAD/app/src/main/logo/windows/cf-lg_312.jpg -------------------------------------------------------------------------------- /app/src/main/resources/garmintools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpickup/GarminTools/HEAD/app/src/main/resources/garmintools.png -------------------------------------------------------------------------------- /app/src/main/resources/instagram-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpickup/GarminTools/HEAD/app/src/main/resources/instagram-32.png -------------------------------------------------------------------------------- /app/src/main/logo/macosx/GarminTools.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpickup/GarminTools/HEAD/app/src/main/logo/macosx/GarminTools.icns -------------------------------------------------------------------------------- /app/src/main/logo/windows/GarminTools.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpickup/GarminTools/HEAD/app/src/main/logo/windows/GarminTools.ico -------------------------------------------------------------------------------- /gpx/src/main/java/com/johnpickup/gpx/PointlessExport.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.gpx; 2 | 3 | public class PointlessExport { 4 | } 5 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/Power.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | public interface Power { 4 | } 5 | -------------------------------------------------------------------------------- /app/src/main/resources/GarminWorkoutCreator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpickup/GarminTools/HEAD/app/src/main/resources/GarminWorkoutCreator.png -------------------------------------------------------------------------------- /app/src/test/resources/ExampleWorkoutSchedule.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpickup/GarminTools/HEAD/app/src/test/resources/ExampleWorkoutSchedule.xls -------------------------------------------------------------------------------- /app/src/test/resources/MultiSportWorkoutSchedule.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpickup/GarminTools/HEAD/app/src/test/resources/MultiSportWorkoutSchedule.xls -------------------------------------------------------------------------------- /app/src/test/resources/com.johnpickup.gpx/SimpleTest.fit: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpickup/GarminTools/HEAD/app/src/test/resources/com.johnpickup.gpx/SimpleTest.fit -------------------------------------------------------------------------------- /app/src/test/resources/com.johnpickup.gpx/BedgeburyBlue.fit: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpickup/GarminTools/HEAD/app/src/test/resources/com.johnpickup.gpx/BedgeburyBlue.fit -------------------------------------------------------------------------------- /parser/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module com.johnpickup.parser { 2 | requires antlr4.runtime; 3 | requires com.johnpickup.common; 4 | exports com.johnpickup.garmin.parser; 5 | } -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/Pace.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | /** 4 | * Created by john on 03/01/2017. 5 | */ 6 | public interface Pace { 7 | } 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/HeartRate.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | /** 4 | * Created by john on 03/01/2017. 5 | */ 6 | public interface HeartRate { 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/calendar/ScheduleWriter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.calendar; 2 | 3 | public interface ScheduleWriter { 4 | void write(Schedule schedule) throws Exception; 5 | } 6 | -------------------------------------------------------------------------------- /gpx/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module com.johnpickup.gpx { 2 | requires jakarta.xml.bind; 3 | requires com.johnpickup.common; 4 | opens com.johnpickup.gpx; 5 | exports com.johnpickup.gpx; 6 | } -------------------------------------------------------------------------------- /.idea/jpa-buddy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/ClojureProjectResolveSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | IDE 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/resources/GarminTools.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Name=garmintools 4 | Exec=bin/garmintools 5 | Comment=A suite of tools for Garmin watches 6 | Icon=garmintools 7 | Terminal=false 8 | Categories=Utility; 9 | -------------------------------------------------------------------------------- /app/src/main/resources/control: -------------------------------------------------------------------------------- 1 | Package: garmintools 2 | Architecture: all 3 | Version: 1.0.0 4 | Section: misc 5 | Maintainer: John Pickup 6 | Priority: optional 7 | Standards-Version: 4.7.0 8 | Description: Garmin Tools 9 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/StepIntensity.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | public enum StepIntensity { 4 | ACTIVE, 5 | REST, 6 | WARMUP, 7 | COOLDOWN, 8 | RECOVERY 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/StepConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.app.garmin.workout.WorkoutStep; 4 | import com.johnpickup.garmin.parser.Step; 5 | 6 | /** 7 | * Created by john on 09/01/2017. 8 | */ 9 | public interface StepConverter { 10 | WorkoutStep convert(Step step); 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/TimeConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.garmin.parser.Time; 4 | 5 | /** 6 | * Convert independent times into Garmin units 7 | */ 8 | public class TimeConverter { 9 | public static double convert(Time time) { 10 | return time.asDouble(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/PowerUnit.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | public enum PowerUnit { 4 | WATTS; 5 | 6 | @Override 7 | public String toString() { 8 | switch (this) { 9 | case WATTS: return "W"; 10 | default: return super.toString(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/HeartRateUnit.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | public enum HeartRateUnit { 4 | BPM; 5 | 6 | @Override 7 | public String toString() { 8 | switch (this) { 9 | case BPM: return "bpm"; 10 | default: return super.toString(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/Sport.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | public enum Sport { 4 | RUNNING, 5 | ROAD_RUNNING, 6 | TRAIL_RUNNING, 7 | CYCLING, 8 | ROAD_CYCLING, 9 | MTB, 10 | SWIMMING, 11 | POOL_SWIMMING, 12 | OPEN_WATER_SWIMMING, 13 | CARDIO, 14 | STRENGTH, 15 | HIIT, 16 | PILATES, 17 | YOGA 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/task/UiTask.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.task; 2 | 3 | import com.johnpickup.app.javafx.TaskArguments; 4 | import javafx.concurrent.Task; 5 | 6 | public abstract class UiTask extends Task { 7 | protected final TaskArguments taskArguments; 8 | 9 | protected UiTask(TaskArguments taskArguments) { 10 | this.taskArguments = taskArguments; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /common/src/main/java/com/johnpickup/garmin/common/unit/PowerTarget.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.common.unit; 2 | 3 | /** 4 | * Power target - either a zone or a custom minimum and maximum power (in subclasses) 5 | */ 6 | public abstract class PowerTarget { 7 | 8 | public abstract Long getGarminLow(); 9 | 10 | public abstract Long getGarminHigh(); 11 | 12 | public abstract Long getTargetValue(); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/garmin/fit/FitGenerator.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.garmin.fit; 2 | 3 | import com.garmin.fit.Mesg; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by john on 31/12/2016. 9 | */ 10 | public interface FitGenerator { 11 | int PRODUCT_ID = 65534; //484; 12 | int SOFTWARE_VERSION = 16; //16023; 13 | short HARDWARE_VERSION = 0; 14 | 15 | List generate(); 16 | } 17 | -------------------------------------------------------------------------------- /common/src/main/java/com/johnpickup/garmin/common/unit/HeartRateTarget.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.common.unit; 2 | 3 | /** 4 | * Heart Rate target - either a zone or a custom minimum and maximum HR (in subclasses) 5 | */ 6 | public abstract class HeartRateTarget{ 7 | 8 | public abstract Long getGarminLow(); 9 | 10 | public abstract Long getGarminHigh(); 11 | 12 | public abstract Long getTargetValue(); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/javafx/AppLauncher.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.javafx; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | public class AppLauncher { 7 | private static final Logger log = LoggerFactory.getLogger(AppLauncher.class); 8 | 9 | public static void main(String[] args) { 10 | Thread.setDefaultUncaughtExceptionHandler((t, e) -> log.error("Unhandled error", e)); 11 | MainForm.main(args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/calendar/Programme.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.calendar; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | 6 | public class Programme { 7 | private final Collection workouts = new ArrayList<>(); 8 | 9 | public void addWorkout(PlannedWorkout workout) { 10 | workouts.add(workout); 11 | } 12 | 13 | public Collection getWorkouts() { 14 | return this.workouts; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/PaceConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.garmin.common.unit.PaceTarget; 4 | import com.johnpickup.garmin.parser.Pace; 5 | 6 | /** 7 | * Interface that pace converters must implement. 8 | * One converter will be implemented for each sub-type of Pace and will emit a corresponding 9 | * instance of a Garmin PaceTarget 10 | */ 11 | public interface PaceConverter { 12 | PaceTarget convert(Pace pace); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/PowerConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.garmin.common.unit.PowerTarget; 4 | import com.johnpickup.garmin.parser.Power; 5 | 6 | /** 7 | * Interface that pace converters must implement. 8 | * One converter will be implemented for each sub-type of Pace and will emit a corresponding 9 | * instance of a Garmin PaceTarget 10 | */ 11 | public interface PowerConverter { 12 | PowerTarget convert(Power power); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/calendar/Scheduler.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.calendar; 2 | 3 | import java.time.LocalDate; 4 | 5 | public class Scheduler { 6 | 7 | public ScheduledWorkout schedule(PlannedWorkout workout, LocalDate raceDate) { 8 | String name = workout.getName(); 9 | String description = workout.getDescription(); 10 | LocalDate date = raceDate.plusDays(workout.getOffset()); 11 | return new ScheduledWorkout(name, description, date); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/HeartRateConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.garmin.common.unit.HeartRateTarget; 4 | import com.johnpickup.garmin.parser.HeartRate; 5 | 6 | /** 7 | * Interface that pace converters must implement. 8 | * One converter will be implemented for each sub-type of Pace and will emit a corresponding 9 | * instance of a Garmin PaceTarget 10 | */ 11 | public interface HeartRateConverter { 12 | HeartRateTarget convert(HeartRate heartRate); 13 | } 14 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/DistanceUnit.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | /** 4 | * Created by john on 03/01/2017. 5 | */ 6 | public enum DistanceUnit { 7 | METRE, 8 | KILOMETRE, 9 | MILE; 10 | 11 | @Override 12 | public String toString() { 13 | switch (this) { 14 | case METRE: return "m"; 15 | case KILOMETRE: return "km"; 16 | case MILE: return "mi"; 17 | default: return super.toString(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /gpx/src/main/resources/xjb/gpx.xjb: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/Step.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | /** 4 | * Created by john on 03/01/2017. 5 | */ 6 | public abstract class Step { 7 | public StepIntensity getStepIntensity() { 8 | return stepIntensity; 9 | } 10 | 11 | public Step() { 12 | this.stepIntensity = null; 13 | } 14 | 15 | public Step(StepIntensity stepIntensity) { 16 | this.stepIntensity = stepIntensity; 17 | } 18 | 19 | protected final StepIntensity stepIntensity; 20 | } 21 | -------------------------------------------------------------------------------- /ROUTES.md: -------------------------------------------------------------------------------- 1 | # Garmin Routes 2 | Converting GPX tracks and routes has never worked seamlessly on my Fenix 3 so I wrote this to perform the conversion in a reliable way. 3 | 4 | Simply load the GPX file in the UI and hit the convert button. The equivalent FIT file will be written to the watch. 5 | 6 | Includes a "reverse" checkbox that creates the same route as the GPX input file but in the opposite direction (start at the finisih, finish at the start). 7 | 8 | Does not attempt to apply any pacing in the resulting course file. Might be worth adding this in future. -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/MinimumPace.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | /** 4 | * Created by john on 03/01/2017. 5 | */ 6 | public class MinimumPace extends PaceLimit { 7 | public MinimumPace(Time time, PaceUnit unit) { 8 | super(time, unit); 9 | } 10 | public MinimumPace(PaceLimit paceLimit) { 11 | super(paceLimit.getTime(), paceLimit.getUnit()); 12 | } 13 | @Override 14 | public String toString() { 15 | return String.format(">%s%s", getTime(), getUnit()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/MaximumPace.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | /** 4 | * Created by john on 03/01/2017. 5 | */ 6 | public class MaximumPace extends PaceLimit { 7 | public MaximumPace(Time time, PaceUnit unit) { 8 | super(time, unit); 9 | } 10 | 11 | public MaximumPace(PaceLimit paceLimit) { 12 | super(paceLimit.getTime(), paceLimit.getUnit()); 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return String.format("<%s%s", getTime(), getUnit()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/ZonePowerConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.garmin.common.unit.PowerTarget; 4 | import com.johnpickup.garmin.common.unit.ZonePowerTarget; 5 | import com.johnpickup.garmin.parser.Power; 6 | import com.johnpickup.garmin.parser.PowerZone; 7 | 8 | public class ZonePowerConverter implements PowerConverter { 9 | @Override 10 | public PowerTarget convert(Power power) { 11 | PowerZone powerZone = (PowerZone) power; 12 | return new ZonePowerTarget(powerZone.getZoneNumber()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/garmin/fit/FitSaver.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.garmin.fit; 2 | 3 | import com.garmin.fit.FileEncoder; 4 | import com.garmin.fit.Fit; 5 | 6 | import java.io.File; 7 | 8 | public class FitSaver { 9 | 10 | public static void save(FitGenerator fitGenerator, String filename) { 11 | save(fitGenerator, new java.io.File(filename)); 12 | } 13 | 14 | public static void save(FitGenerator fitGenerator, File file) { 15 | FileEncoder encode = new FileEncoder( file, Fit.ProtocolVersion.V1_0); 16 | encode.write(fitGenerator.generate()); 17 | encode.close(); 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/javafx/UiAppender.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.javafx; 2 | 3 | import ch.qos.logback.classic.spi.ILoggingEvent; 4 | import ch.qos.logback.core.AppenderBase; 5 | 6 | import java.util.function.Consumer; 7 | 8 | public class UiAppender extends AppenderBase { 9 | private final Consumer messageConsumer; 10 | 11 | public UiAppender(Consumer messageConsumer) { 12 | this.messageConsumer = messageConsumer; 13 | } 14 | 15 | @Override 16 | protected void append(ILoggingEvent event) { 17 | messageConsumer.accept(event.getFormattedMessage()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/calendar/ScheduleGenerator.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.calendar; 2 | 3 | import java.time.LocalDate; 4 | 5 | public class ScheduleGenerator { 6 | 7 | private static Scheduler scheduler = new Scheduler(); 8 | 9 | public static Schedule generate(Programme programme, LocalDate raceDate) { 10 | Schedule result = new Schedule(); 11 | 12 | for (PlannedWorkout workout : programme.getWorkouts()) { 13 | ScheduledWorkout scheduledWorkout = scheduler.schedule(workout, raceDate); 14 | result.add(scheduledWorkout); 15 | } 16 | 17 | return result; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/ZoneHeartRateConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.garmin.common.unit.HeartRateTarget; 4 | import com.johnpickup.garmin.common.unit.ZoneHeartRateTarget; 5 | import com.johnpickup.garmin.parser.HeartRate; 6 | import com.johnpickup.garmin.parser.HeartRateZone; 7 | 8 | public class ZoneHeartRateConverter implements HeartRateConverter { 9 | @Override 10 | public HeartRateTarget convert(HeartRate heartRate) { 11 | HeartRateZone heartRateZone = (HeartRateZone) heartRate; 12 | return new ZoneHeartRateTarget(heartRateZone.getZoneNumber()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/OpenStepConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.app.garmin.workout.OpenWorkoutStep; 4 | import com.johnpickup.app.garmin.workout.WorkoutStep; 5 | import com.johnpickup.garmin.parser.Step; 6 | 7 | /** 8 | * Convert independent distance steps into Garmin Workout Distance Steps 9 | */ 10 | public class OpenStepConverter implements StepConverter { 11 | @Override 12 | public WorkoutStep convert(Step step) { 13 | OpenWorkoutStep openWorkoutStep = new OpenWorkoutStep(StepIntensityConverter.convert(step.getStepIntensity())); 14 | return openWorkoutStep; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /common/src/main/java/com/johnpickup/garmin/common/unit/ZonePowerTarget.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.common.unit; 2 | 3 | public class ZonePowerTarget extends PowerTarget { 4 | private final Long zone; 5 | 6 | public ZonePowerTarget(Long zone) { 7 | this.zone = zone; 8 | } 9 | 10 | @Override 11 | public Long getGarminLow() { 12 | return 0L; 13 | } 14 | 15 | @Override 16 | public Long getGarminHigh() { 17 | return 0L; 18 | } 19 | 20 | @Override 21 | public Long getTargetValue() { 22 | return zone; 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return "PZ" + zone; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/test/java/com/johnpickup/app/parser/PaceRangeTest.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.parser; 2 | 3 | import com.johnpickup.garmin.parser.PaceRange; 4 | import com.johnpickup.garmin.parser.PaceUnit; 5 | import com.johnpickup.garmin.parser.Time; 6 | import org.junit.Test; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | 10 | /** 11 | * Created by john on 10/01/2017. 12 | */ 13 | public class PaceRangeTest { 14 | @Test 15 | public void testToString() throws Exception { 16 | String actual = new PaceRange(new Time(10,30), new Time(8,0), PaceUnit.MIN_PER_MILE).toString(); 17 | String expected = "8:00-10:30/mi"; 18 | assertEquals(expected, actual); 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /common/src/main/java/com/johnpickup/garmin/common/unit/PowerUnit.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.common.unit; 2 | 3 | public enum PowerUnit { 4 | WATTS("watts","W"); 5 | 6 | private final String description; 7 | 8 | final String shortName; 9 | 10 | PowerUnit(String description, String shortName) { 11 | this.description = description; 12 | this.shortName = shortName; 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return description; 18 | } 19 | 20 | public String getDescription() { 21 | return this.description; 22 | } 23 | 24 | public String getShortName() { 25 | return this.shortName; 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /common/src/main/java/com/johnpickup/garmin/common/unit/ZoneHeartRateTarget.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.common.unit; 2 | 3 | public class ZoneHeartRateTarget extends HeartRateTarget { 4 | private final Long zone; 5 | 6 | public ZoneHeartRateTarget(Long zone) { 7 | this.zone = zone; 8 | } 9 | 10 | @Override 11 | public Long getGarminLow() { 12 | return 0L; 13 | } 14 | 15 | @Override 16 | public Long getGarminHigh() { 17 | return 0L; 18 | } 19 | 20 | @Override 21 | public Long getTargetValue() { 22 | return zone; 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return "Z" + zone; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /bundle/src/assembly/macos.xml: -------------------------------------------------------------------------------- 1 | 3 | macos 4 | 5 | zip 6 | 7 | 8 | 9 | ${project.basedir}/../installer 10 | / 11 | 12 | *.pkg 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /bundle/src/assembly/linux.xml: -------------------------------------------------------------------------------- 1 | 3 | linux 4 | 5 | zip 6 | 7 | 8 | 9 | ${project.basedir}/../installer 10 | / 11 | 12 | *.AppImage 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /bundle/src/assembly/windows.xml: -------------------------------------------------------------------------------- 1 | 3 | windows 4 | 5 | zip 6 | 7 | 8 | 9 | ${project.basedir}/../installer/GarminTools 10 | / 11 | 12 | **/* 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /common/src/main/java/com/johnpickup/garmin/common/unit/HeartRateUnit.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.common.unit; 2 | 3 | public enum HeartRateUnit { 4 | BEATS_PER_MINUTE("beats per minute","bpm"); 5 | 6 | private final String description; 7 | 8 | final String shortName; 9 | 10 | HeartRateUnit(String description, String shortName) { 11 | this.description = description; 12 | this.shortName = shortName; 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return description; 18 | } 19 | 20 | public String getDescription() { 21 | return this.description; 22 | } 23 | 24 | public String getShortName() { 25 | return this.shortName; 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /app/src/test/java/com/johnpickup/app/parser/TimeTextParserTest.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.parser; 2 | 3 | import com.johnpickup.garmin.parser.Time; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | 9 | /** 10 | * Created by john on 03/01/2017. 11 | */ 12 | public class TimeTextParserTest { 13 | private TimeTextParser classUnderTest; 14 | @Before 15 | public void setUp() throws Exception { 16 | classUnderTest = new TimeTextParser(); 17 | 18 | } 19 | 20 | @Test 21 | public void parseTime() throws Exception { 22 | Time actual = classUnderTest.parse("08:30"); 23 | Time expected = new Time(8,30); 24 | assertEquals(expected, actual); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/garmin/WorkoutSaver.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.garmin; 2 | 3 | import com.garmin.fit.FileEncoder; 4 | import com.garmin.fit.Fit; 5 | import com.johnpickup.app.garmin.fit.FitGenerator; 6 | 7 | import java.io.File; 8 | 9 | /** 10 | * Class that can save output from a FitGenerator to a fie 11 | */ 12 | public class WorkoutSaver { 13 | 14 | public void save(FitGenerator fitGenerator, String filename) { 15 | save(fitGenerator, new java.io.File(filename)); 16 | } 17 | 18 | public void save(FitGenerator fitGenerator, File file) { 19 | FileEncoder encode = new FileEncoder( file, Fit.ProtocolVersion.V1_0); 20 | encode.write(fitGenerator.generate()); 21 | encode.close(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/StepIntensityConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.garmin.fit.Intensity; 4 | import com.johnpickup.garmin.parser.StepIntensity; 5 | 6 | public class StepIntensityConverter { 7 | public static Intensity convert(StepIntensity stepIntensity) { 8 | if (stepIntensity == null) return Intensity.ACTIVE; // default if not specified 9 | return switch (stepIntensity) { 10 | case REST -> Intensity.REST; 11 | case ACTIVE -> Intensity.ACTIVE; 12 | case WARMUP -> Intensity.WARMUP; 13 | case COOLDOWN -> Intensity.COOLDOWN; 14 | case RECOVERY -> Intensity.RECOVERY; 15 | default -> Intensity.INVALID; 16 | }; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/gpx/GpxReader.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.gpx; 2 | 3 | import com.johnpickup.gpx.GpxType; 4 | import jakarta.xml.bind.JAXBContext; 5 | import jakarta.xml.bind.JAXBException; 6 | import jakarta.xml.bind.JAXBIntrospector; 7 | import jakarta.xml.bind.Unmarshaller; 8 | 9 | import java.io.File; 10 | import java.io.FileInputStream; 11 | import java.io.FileNotFoundException; 12 | 13 | public class GpxReader { 14 | public GpxType readGpxFile(File file) throws JAXBException, FileNotFoundException { 15 | JAXBContext jc = JAXBContext.newInstance("com.johnpickup.gpx"); 16 | Unmarshaller um = jc.createUnmarshaller(); 17 | GpxType gpxType = (GpxType) JAXBIntrospector.getValue(um.unmarshal(new FileInputStream(file))); 18 | return gpxType; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/MaximumPaceConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.garmin.common.unit.PaceTarget; 4 | import com.johnpickup.garmin.common.unit.PaceUnit; 5 | import com.johnpickup.garmin.parser.MaximumPace; 6 | import com.johnpickup.garmin.parser.Pace; 7 | 8 | /** 9 | * Created by john on 09/01/2017. 10 | */ 11 | public class MaximumPaceConverter implements PaceConverter { 12 | @Override 13 | public PaceTarget convert(Pace pace) { 14 | MaximumPace maximumPace = (MaximumPace)pace; 15 | double max = TimeConverter.convert(maximumPace.getTime()); 16 | double min = max / 2.0; 17 | PaceUnit unit = PaceUnitConverter.convert(maximumPace.getUnit()); 18 | return new PaceTarget(min, max, unit); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | -------------------------------------------------------------------------------- /common/src/main/java/com/johnpickup/garmin/common/unit/DistanceUnit.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.common.unit; 2 | 3 | /** 4 | * Created by john on 22/11/2016. 5 | */ 6 | public enum DistanceUnit { 7 | METRE("metre","m"), 8 | KILOMETRE("kilometre","km"), 9 | MILE("mile","mi"); 10 | 11 | private final String description; 12 | 13 | final String shortName; 14 | 15 | DistanceUnit(String description, String shortName) { 16 | this.description = description; 17 | this.shortName = shortName; 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return description; 23 | } 24 | 25 | public String getDescription() { 26 | return this.description; 27 | } 28 | 29 | public String getShortName() { 30 | return this.shortName; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/MinimumPaceConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.garmin.common.unit.PaceTarget; 4 | import com.johnpickup.garmin.common.unit.PaceUnit; 5 | import com.johnpickup.garmin.parser.MaximumPace; 6 | import com.johnpickup.garmin.parser.Pace; 7 | 8 | /** 9 | * Created by john on 09/01/2017. 10 | */ 11 | public class MinimumPaceConverter implements PaceConverter { 12 | @Override 13 | public PaceTarget convert(Pace pace) { 14 | MaximumPace maximumPace = (MaximumPace)pace; 15 | double min = TimeConverter.convert(maximumPace.getTime()); 16 | double max = min * 2.0; 17 | PaceUnit unit = PaceUnitConverter.convert(maximumPace.getUnit()); 18 | return new PaceTarget(maximumPace.toString(),min, max, unit); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/test/java/com/johnpickup/app/gpx/GpxReaderTest.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.gpx; 2 | 3 | import com.johnpickup.gpx.GpxType; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import java.io.File; 8 | 9 | import static org.junit.Assert.assertNotNull; 10 | 11 | public class GpxReaderTest { 12 | private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GpxReaderTest.class); 13 | private GpxReader gpxReader; 14 | 15 | @Before 16 | public void setUp() { 17 | gpxReader = new GpxReader(); 18 | } 19 | 20 | @Test 21 | public void readGpxFile() throws Exception { 22 | GpxType gpxType = gpxReader.readGpxFile(new File("src/test/resources/com.johnpickup.gpx/BedgeburyBlue.gpx")); 23 | assertNotNull(gpxType); 24 | log.info("{}", gpxType.getRte()); 25 | } 26 | } -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/AntlrErrorHandler.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import org.antlr.v4.runtime.DefaultErrorStrategy; 4 | import org.antlr.v4.runtime.Parser; 5 | import org.antlr.v4.runtime.RecognitionException; 6 | 7 | /** 8 | * Created by john on 16/01/2017. 9 | */ 10 | public class AntlrErrorHandler extends DefaultErrorStrategy { 11 | private boolean hadError = false; 12 | private String errorMessage = null; 13 | 14 | @Override 15 | public void reportError(Parser recognizer, RecognitionException e) { 16 | hadError = true; 17 | errorMessage = e.getMessage(); 18 | } 19 | 20 | public boolean isHadError() { 21 | return this.hadError; 22 | } 23 | 24 | public String getErrorMessage() { 25 | return this.errorMessage; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/test/resources/com.johnpickup.gpx/SimpleTest.gpx: -------------------------------------------------------------------------------- 1 | 2 | Simple TestSimple Test183.8162.6125.9127.1123.4131.1146.7186.5 4 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/PaceRangeConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.garmin.common.unit.PaceTarget; 4 | import com.johnpickup.garmin.common.unit.PaceUnit; 5 | import com.johnpickup.garmin.parser.Pace; 6 | import com.johnpickup.garmin.parser.PaceRange; 7 | 8 | /** 9 | * Convert an independent pace range into a Garmin pace target 10 | */ 11 | public class PaceRangeConverter implements PaceConverter { 12 | @Override 13 | public PaceTarget convert(Pace pace) { 14 | PaceRange paceRange = (PaceRange)pace; 15 | double min = TimeConverter.convert(paceRange.getMinimum()); 16 | double max = TimeConverter.convert(paceRange.getMaximum()); 17 | PaceUnit unit = PaceUnitConverter.convert(paceRange.getUnit()); 18 | return new PaceTarget(min, max, unit); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/OpenStep.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | public class OpenStep extends Step { 4 | public OpenStep() { 5 | super(); 6 | } 7 | 8 | public OpenStep(StepIntensity stepIntensity) { 9 | super(stepIntensity); 10 | } 11 | 12 | @Override 13 | public boolean equals(Object o) { 14 | if (this == o) return true; 15 | if (o == null || getClass() != o.getClass()) return false; 16 | OpenStep that = (OpenStep) o; 17 | return stepIntensity == that.stepIntensity; 18 | } 19 | 20 | protected boolean canEqual(final Object other) { 21 | return other instanceof OpenStep; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return "Open" + (stepIntensity==null?"":("|"+stepIntensity)); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/DiatanceUnitConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.garmin.parser.DistanceUnit; 4 | 5 | /** 6 | * Convert independent distance units into the Garmin type 7 | */ 8 | public class DiatanceUnitConverter { 9 | public static com.johnpickup.garmin.common.unit.DistanceUnit convert(DistanceUnit unit) { 10 | switch (unit) { 11 | case KILOMETRE: 12 | return com.johnpickup.garmin.common.unit.DistanceUnit.KILOMETRE; 13 | case METRE: 14 | return com.johnpickup.garmin.common.unit.DistanceUnit.METRE; 15 | case MILE: 16 | return com.johnpickup.garmin.common.unit.DistanceUnit.MILE; 17 | default: 18 | throw new RuntimeException("Unkown distance unit " + unit); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/task/ScheduleFitTask.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.task; 2 | 3 | import com.johnpickup.app.GarminScheduleGenerator; 4 | import com.johnpickup.app.javafx.TaskArguments; 5 | 6 | public class ScheduleFitTask extends UiTask { 7 | public ScheduleFitTask(TaskArguments taskArguments) { 8 | super(taskArguments); 9 | } 10 | 11 | @Override 12 | protected String call() throws Exception { 13 | updateMessage("Converting " + taskArguments.getInputFile().getPath() + " to FIT files in " + taskArguments.getOutputDir().getPath()); 14 | GarminScheduleGenerator generator = new GarminScheduleGenerator(); 15 | generator.generate(taskArguments.getInputFile(), taskArguments.getOutputDir()); 16 | updateMessage("Completed converting " + taskArguments.getInputFile().getPath()); 17 | return "OK"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module com.johnpickup.app { 2 | requires javafx.controls; 3 | requires javafx.fxml; 4 | 5 | requires org.controlsfx.controls; 6 | requires com.dlsc.formsfx; 7 | requires org.kordamp.bootstrapfx.core; 8 | requires org.slf4j; 9 | requires antlr4.runtime; 10 | requires fit; 11 | requires org.apache.poi.poi; 12 | requires biweekly; 13 | requires jakarta.xml.bind; 14 | requires org.apache.poi.ooxml; 15 | requires org.apache.commons.io; 16 | requires ch.qos.logback.classic; 17 | requires ch.qos.logback.core; 18 | 19 | requires com.johnpickup.common; 20 | requires com.johnpickup.gpx; 21 | requires com.johnpickup.parser; 22 | 23 | opens com.johnpickup.app.javafx; 24 | exports com.johnpickup.app.javafx; 25 | exports com.johnpickup.app.task; 26 | opens com.johnpickup.app.task; 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/task/ScheduleIcalTask.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.task; 2 | 3 | import com.johnpickup.app.CalendarScheduleGenerator; 4 | import com.johnpickup.app.javafx.TaskArguments; 5 | 6 | public class ScheduleIcalTask extends UiTask { 7 | public ScheduleIcalTask(TaskArguments taskArguments) { 8 | super(taskArguments); 9 | } 10 | 11 | @Override 12 | protected String call() throws Exception { 13 | updateMessage("Converting " + taskArguments.getInputFile().getPath() + " to iCal file in " + taskArguments.getOutputDir().getPath()); 14 | CalendarScheduleGenerator generator = new CalendarScheduleGenerator(); 15 | generator.generate(taskArguments.getInputFile(), taskArguments.getOutputDir()); 16 | updateMessage("Completed converting " + taskArguments.getInputFile().getPath()); 17 | return "OK"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/TimeStepConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.app.garmin.workout.TimeWorkoutStep; 4 | import com.johnpickup.app.garmin.workout.WorkoutStep; 5 | import com.johnpickup.garmin.common.unit.Time; 6 | import com.johnpickup.garmin.parser.Step; 7 | import com.johnpickup.garmin.parser.TimeStep; 8 | 9 | /** 10 | * Convert independent distance steps into Garmin Workout Distance Steps 11 | */ 12 | public class TimeStepConverter implements StepConverter { 13 | @Override 14 | public WorkoutStep convert(Step step) { 15 | TimeStep timeStep = (TimeStep)step; 16 | 17 | Time t = new Time(timeStep.getTime().asDouble() * 60); 18 | TimeWorkoutStep timeWorkoutStep = new TimeWorkoutStep(StepIntensityConverter.convert(step.getStepIntensity()), t); 19 | return timeWorkoutStep; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /common/src/main/java/com/johnpickup/garmin/common/unit/Time.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.common.unit; 2 | 3 | /** 4 | * Garmin time unit. value is in seconds. 5 | * Value in fit files is in units of 0.1s so we multiply by 10 when emitting this 6 | */ 7 | public class Time { 8 | private final double value; 9 | 10 | public Time(double value) { 11 | this.value = value; 12 | } 13 | 14 | public Float toGarminTime() { 15 | return (float)(value * 10); 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | int seconds = (int)value % 60; 21 | int minutes = (int)(value / 60) % 60; 22 | int hours = (int)(value / 3600); 23 | if (hours > 0) 24 | return String.format("%02d:%02d:%02d", hours, minutes, seconds); 25 | else 26 | return String.format("%02d:%02d", minutes, seconds); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /common/src/main/java/com/johnpickup/garmin/common/unit/PaceUnit.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.common.unit; 2 | 3 | 4 | /** 5 | * Created by john on 30/12/2016. 6 | */ 7 | public enum PaceUnit { 8 | MIN_PER_MILE("min/mile","/mi"), 9 | MIN_PER_KILOMETRE("min/km","/km"), 10 | MILE_PER_HOUR("mile/hr","MPH"), 11 | KILOMETRE_PER_HOUR("km/hr","kph"); 12 | 13 | private final String description; 14 | 15 | final String shortName; 16 | 17 | PaceUnit(String description, String shortName) { 18 | this.description = description; 19 | this.shortName = shortName; 20 | } 21 | 22 | @Override 23 | public String toString() { 24 | return description; 25 | } 26 | 27 | public String getDescription() { 28 | return this.description; 29 | } 30 | 31 | public String getShortName() { 32 | return this.shortName; 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/calendar/ScheduleConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.calendar; 2 | 3 | import java.time.LocalDate; 4 | 5 | import static java.time.temporal.ChronoUnit.DAYS; 6 | 7 | public class ScheduleConverter { 8 | 9 | public static Programme convertToProgramme(Schedule schedule) { 10 | Programme result = new Programme(); 11 | 12 | LocalDate raceDate = schedule.getRaceDate(); 13 | 14 | for (ScheduledWorkout workout : schedule.getWorkouts()) { 15 | String name = workout.getName(); 16 | String description = workout.getDescription(); 17 | long days = DAYS.between(workout.getDate(), raceDate); 18 | long offset = -days; 19 | PlannedWorkout plannedWorkout = new PlannedWorkout(name, description, offset); 20 | result.addWorkout(plannedWorkout); 21 | } 22 | 23 | return result; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/src/test/java/com/johnpickup/app/garmin/unit/DistanceTest.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.garmin.unit; 2 | 3 | import com.johnpickup.garmin.common.unit.Distance; 4 | import com.johnpickup.garmin.common.unit.DistanceUnit; 5 | 6 | import static org.junit.Assert.assertEquals; 7 | 8 | public class DistanceTest { 9 | @org.junit.Test 10 | public void toStringTestKm() throws Exception { 11 | Distance d = new Distance(1.0, DistanceUnit.KILOMETRE); 12 | assertEquals("1km", d.toString()); 13 | } 14 | 15 | @org.junit.Test 16 | public void toStringTestMile() throws Exception { 17 | Distance d = new Distance(1.2, DistanceUnit.MILE); 18 | assertEquals("1.2mi", d.toString()); 19 | } 20 | 21 | @org.junit.Test 22 | public void toStringTestMetre() throws Exception { 23 | Distance d = new Distance(400, DistanceUnit.METRE); 24 | assertEquals("400m", d.toString()); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/util/Haversine.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.util; 2 | 3 | import static java.lang.Math.*; 4 | 5 | public class Haversine { 6 | private final static double DM_MEAN_RADIUS = 6371000.0d; 7 | 8 | /** 9 | * Compute the distance between two points on the surface of the Earth 10 | * @param rlat1 latitude of first point in radians 11 | * @param rlong1 longitude of first point in radians 12 | * @param rlat2 latitude of second point in radians 13 | * @param rlong2 longitude of second point in radians 14 | * @return distance in metres 15 | */ 16 | public static double distance(double rlat1, double rlong1, double rlat2, double rlong2) 17 | { 18 | double hs1, hs2; 19 | hs1 = sin((rlat1 - rlat2) / 2.0d); 20 | hs2 = sin((rlong1 - rlong2) / 2.0d); 21 | return 2.0d * DM_MEAN_RADIUS * asin(sqrt(hs1 * hs1 + cos(rlat1) * cos(rlat2) * hs2 * hs2)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/CustomPowerConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.garmin.common.unit.CustomPowerTarget; 4 | import com.johnpickup.garmin.common.unit.PowerTarget; 5 | import com.johnpickup.garmin.common.unit.PowerUnit; 6 | import com.johnpickup.garmin.parser.Power; 7 | import com.johnpickup.garmin.parser.PowerRange; 8 | 9 | public class CustomPowerConverter implements PowerConverter { 10 | @Override 11 | public PowerTarget convert(Power power) { 12 | PowerRange powerRange = (PowerRange) power; 13 | PowerUnit unit; 14 | switch (powerRange.getUnit()) { 15 | case WATTS: 16 | unit = PowerUnit.WATTS; 17 | break; 18 | default: 19 | throw new RuntimeException("Unknown power unit: " + powerRange.getUnit()); 20 | } 21 | 22 | return new CustomPowerTarget(powerRange.getMinimum(), powerRange. getMaximum(), unit); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/OpenPowerStepConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.app.garmin.workout.OpenPowerWorkoutStep; 4 | import com.johnpickup.app.garmin.workout.WorkoutStep; 5 | import com.johnpickup.garmin.common.unit.PowerTarget; 6 | import com.johnpickup.garmin.parser.OpenPowerStep; 7 | import com.johnpickup.garmin.parser.Step; 8 | 9 | /** 10 | * Convert independent pace steps into the Garmin equivalent 11 | */ 12 | public class OpenPowerStepConverter implements StepConverter { 13 | @Override 14 | public WorkoutStep convert(Step step) { 15 | OpenPowerStep openPowerStep = (OpenPowerStep)step; 16 | 17 | PowerTarget powerTarget = PowerConverterFactory.getInstance() 18 | .getPowerConverter(openPowerStep.getPower()) 19 | .convert(openPowerStep.getPower()); 20 | 21 | return new OpenPowerWorkoutStep(StepIntensityConverter.convert(step.getStepIntensity()), powerTarget); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/PaceUnit.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | /** 4 | * Created by john on 03/01/2017. 5 | */ 6 | public enum PaceUnit { 7 | MIN_PER_MILE, 8 | MIN_PER_KILOMETRE, 9 | MILE_PER_HOUR, 10 | KILOMETRE_PER_HOUR; 11 | 12 | public static PaceUnit perDistanceUnit(DistanceUnit distanceUnit) { 13 | switch (distanceUnit) { 14 | case KILOMETRE: 15 | return MIN_PER_KILOMETRE; 16 | case MILE: 17 | return MIN_PER_MILE; 18 | default: 19 | return null; 20 | } 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | switch (this) { 26 | case KILOMETRE_PER_HOUR: return "kph"; 27 | case MILE_PER_HOUR: return "mph"; 28 | case MIN_PER_KILOMETRE: return "/km"; 29 | case MIN_PER_MILE: return "/mi"; 30 | default: return super.toString(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/PaceUnitConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.garmin.parser.PaceUnit; 4 | 5 | /** 6 | * Convert independent pace units into Garmin pace units 7 | */ 8 | public class PaceUnitConverter { 9 | public static com.johnpickup.garmin.common.unit.PaceUnit convert(PaceUnit unit) { 10 | switch (unit) { 11 | case KILOMETRE_PER_HOUR: 12 | return com.johnpickup.garmin.common.unit.PaceUnit.KILOMETRE_PER_HOUR; 13 | case MILE_PER_HOUR: 14 | return com.johnpickup.garmin.common.unit.PaceUnit.MILE_PER_HOUR; 15 | case MIN_PER_KILOMETRE: 16 | return com.johnpickup.garmin.common.unit.PaceUnit.MIN_PER_KILOMETRE; 17 | case MIN_PER_MILE: 18 | return com.johnpickup.garmin.common.unit.PaceUnit.MIN_PER_MILE; 19 | default: 20 | throw new RuntimeException("Unknown pace unit " + unit); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/task/GpxTask.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.task; 2 | 3 | import com.johnpickup.app.GarminRouteGenerator; 4 | import com.johnpickup.app.javafx.TaskArguments; 5 | 6 | public class GpxTask extends UiTask { 7 | public GpxTask(TaskArguments taskArguments) { 8 | super(taskArguments); 9 | } 10 | 11 | @Override 12 | protected String call() throws Exception { 13 | updateMessage("Converting " + taskArguments.getInputFile().getPath() + " to FIT file in " + taskArguments.getOutputDir().getPath()); 14 | GarminRouteGenerator generator = new GarminRouteGenerator(); 15 | boolean reverse = taskArguments.getOptions()!=null && taskArguments.getOptions().containsKey("reverse") && ((boolean) taskArguments.getOptions().get("reverse")); 16 | generator.convert(taskArguments.getInputFile(), taskArguments.getOutputDir(), reverse); 17 | updateMessage("Completed converting " + taskArguments.getInputFile().getPath()); 18 | return "OK"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/OpenPaceStepConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.app.garmin.workout.OpenPaceWorkoutStep; 4 | import com.johnpickup.app.garmin.workout.WorkoutStep; 5 | import com.johnpickup.garmin.common.unit.PaceTarget; 6 | import com.johnpickup.garmin.parser.OpenPaceStep; 7 | import com.johnpickup.garmin.parser.Step; 8 | 9 | /** 10 | * Convert independent pace steps into the Garmin equivalent 11 | */ 12 | public class OpenPaceStepConverter implements StepConverter { 13 | @Override 14 | public WorkoutStep convert(Step step) { 15 | OpenPaceStep distancePaceStep = (OpenPaceStep)step; 16 | 17 | PaceTarget p = PaceConverterFactory.getInstance().getPaceConverter(distancePaceStep.getPace()).convert(distancePaceStep.getPace()); 18 | OpenPaceWorkoutStep openPaceWorkoutStep = 19 | new OpenPaceWorkoutStep(StepIntensityConverter.convert(step.getStepIntensity()), p); 20 | return openPaceWorkoutStep; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/PaceName.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * Created by john on 07/01/2017. 7 | */ 8 | public class PaceName implements Pace { 9 | private final String name; 10 | 11 | public PaceName(String name) { 12 | this.name = name; 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return name; 18 | } 19 | 20 | @Override 21 | public boolean equals(Object o) { 22 | if (this == o) return true; 23 | if (o == null || getClass() != o.getClass()) return false; 24 | PaceName paceName = (PaceName) o; 25 | return Objects.equals(name, paceName.name); 26 | } 27 | 28 | @Override 29 | public int hashCode() { 30 | return Objects.hash(name); 31 | } 32 | 33 | protected boolean canEqual(final Object other) { 34 | return other instanceof PaceName; 35 | } 36 | 37 | public String getName() { 38 | return this.name; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/javafx/MainForm.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.javafx; 2 | 3 | import javafx.application.Application; 4 | import javafx.fxml.FXMLLoader; 5 | import javafx.scene.Scene; 6 | import javafx.scene.image.Image; 7 | import javafx.stage.Stage; 8 | 9 | import java.io.IOException; 10 | public class MainForm extends Application { 11 | 12 | @Override 13 | public void start(Stage stage) throws IOException { 14 | FXMLLoader fxmlLoader = new FXMLLoader(MainForm.class.getResource("/javafx/main-form.fxml")); 15 | Scene scene = new Scene(fxmlLoader.load(), 600, 440); 16 | stage.setTitle("Garmin Tools"); 17 | stage.setScene(scene); 18 | stage.getIcons().add( 19 | new Image(MainForm.class.getResourceAsStream( "/GarminWorkoutCreator.png" ))); 20 | MainFormController controller = fxmlLoader.getController(); 21 | controller.init(); 22 | 23 | stage.show(); 24 | } 25 | 26 | public static void main(String[] args) { 27 | launch(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/PaceNameConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.garmin.common.unit.PaceTarget; 4 | import com.johnpickup.garmin.parser.Pace; 5 | import com.johnpickup.garmin.parser.PaceName; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * Convert an independent pace range into a Garmin pace target 11 | */ 12 | public class PaceNameConverter implements PaceConverter { 13 | private final Map namedPaceTargets; 14 | 15 | public PaceNameConverter(Map namedPaceTargets) { 16 | this.namedPaceTargets = namedPaceTargets; 17 | } 18 | 19 | @Override 20 | public PaceTarget convert(Pace pace) { 21 | PaceName paceName = (PaceName)pace; 22 | String name = paceName.getName(); 23 | if (namedPaceTargets.containsKey(name)) { 24 | return namedPaceTargets.get(name); 25 | } 26 | else { 27 | throw new RuntimeException("Undefined named pace " + name); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/DistanceStepConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.app.garmin.workout.DistanceWorkoutStep; 4 | import com.johnpickup.app.garmin.workout.WorkoutStep; 5 | import com.johnpickup.garmin.common.unit.Distance; 6 | import com.johnpickup.garmin.parser.DistanceStep; 7 | import com.johnpickup.garmin.parser.Step; 8 | 9 | /** 10 | * Convert independent distance steps into Garmin Workout Distance Steps 11 | */ 12 | public class DistanceStepConverter implements StepConverter { 13 | @Override 14 | public WorkoutStep convert(Step step) { 15 | DistanceStep distanceStep = (DistanceStep)step; 16 | 17 | Distance d = new Distance( 18 | distanceStep.getDistance().getQuantity(), 19 | DiatanceUnitConverter.convert(distanceStep.getDistance().getUnit())); 20 | DistanceWorkoutStep distanceWorkoutStep = 21 | new DistanceWorkoutStep(StepIntensityConverter.convert(step.getStepIntensity()), d); 22 | return distanceWorkoutStep; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/CustomHeartRateConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.garmin.common.unit.CustomHeartRateTarget; 4 | import com.johnpickup.garmin.common.unit.HeartRateTarget; 5 | import com.johnpickup.garmin.common.unit.HeartRateUnit; 6 | import com.johnpickup.garmin.parser.HeartRate; 7 | import com.johnpickup.garmin.parser.HeartRateRange; 8 | 9 | public class CustomHeartRateConverter implements HeartRateConverter { 10 | @Override 11 | public HeartRateTarget convert(HeartRate heartRate) { 12 | HeartRateRange heartRateRange = (HeartRateRange) heartRate; 13 | HeartRateUnit unit; 14 | switch (heartRateRange.getUnit()) { 15 | case BPM: 16 | unit = HeartRateUnit.BEATS_PER_MINUTE; 17 | break; 18 | default: 19 | throw new RuntimeException("Unknown heart rate unit: " + heartRateRange.getUnit()); 20 | } 21 | 22 | return new CustomHeartRateTarget(heartRateRange.getMinimum(), heartRateRange. getMaximum(), unit); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/garmin/workout/WorkoutStep.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.garmin.workout; 2 | 3 | import com.garmin.fit.Intensity; 4 | import com.garmin.fit.WorkoutStepMesg; 5 | 6 | import java.util.List; 7 | import java.util.Optional; 8 | 9 | public abstract class WorkoutStep { 10 | private static int stepIndex = 0; 11 | 12 | public WorkoutStep(Intensity intensity) { 13 | this.intensity = intensity; 14 | } 15 | 16 | protected final Intensity intensity; 17 | 18 | public static void startNewWorkout() { 19 | stepIndex = 0; 20 | } 21 | 22 | protected abstract List generateWorkoutSteps(); 23 | public abstract String getName(); 24 | protected Integer generateWorkoutStepIndex() { 25 | return stepIndex++; 26 | } 27 | protected String intensityDescription() { 28 | return Optional.ofNullable(intensity).map(Enum::toString).orElse(""); 29 | } 30 | protected String nameWithIntensity() { 31 | return intensity == null ? getName() : getName() + " | " + intensityDescription(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/parser/PaceTextParser.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.parser; 2 | 3 | import com.johnpickup.garmin.parser.Pace; 4 | import com.johnpickup.garmin.parser.WorkoutLexer; 5 | import com.johnpickup.garmin.parser.WorkoutParser; 6 | import org.antlr.v4.runtime.CharStream; 7 | import org.antlr.v4.runtime.CharStreams; 8 | import org.antlr.v4.runtime.CommonTokenStream; 9 | import org.antlr.v4.runtime.TokenStream; 10 | 11 | import java.io.ByteArrayInputStream; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | 15 | 16 | /** 17 | * Created by john on 03/01/2017. 18 | */ 19 | public class PaceTextParser { 20 | public Pace parse(String inputText) throws IOException { 21 | InputStream input = new ByteArrayInputStream(inputText.getBytes()); 22 | CharStream charStream = CharStreams.fromStream(input); 23 | WorkoutLexer lexer = new WorkoutLexer(charStream); 24 | TokenStream tokens = new CommonTokenStream(lexer); 25 | WorkoutParser parser = new WorkoutParser(tokens); 26 | return parser.pace().value; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/parser/TimeTextParser.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.parser; 2 | 3 | import com.johnpickup.garmin.parser.Time; 4 | import com.johnpickup.garmin.parser.WorkoutLexer; 5 | import com.johnpickup.garmin.parser.WorkoutParser; 6 | import org.antlr.v4.runtime.CharStream; 7 | import org.antlr.v4.runtime.CharStreams; 8 | import org.antlr.v4.runtime.CommonTokenStream; 9 | import org.antlr.v4.runtime.TokenStream; 10 | 11 | import java.io.ByteArrayInputStream; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | 15 | 16 | /** 17 | * Created by john on 03/01/2017. 18 | */ 19 | public class TimeTextParser { 20 | public Time parse(String inputText) throws IOException { 21 | InputStream input = new ByteArrayInputStream(inputText.getBytes()); 22 | CharStream charStream = CharStreams.fromStream(input); 23 | WorkoutLexer lexer = new WorkoutLexer(charStream); 24 | TokenStream tokens = new CommonTokenStream(lexer); 25 | WorkoutParser parser = new WorkoutParser(tokens); 26 | return parser.time().value; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/calendar/Schedule.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.calendar; 2 | 3 | import java.time.LocalDate; 4 | import java.util.ArrayList; 5 | import java.util.Collection; 6 | 7 | public class Schedule { 8 | private final Collection workouts = new ArrayList<>(); 9 | 10 | public Collection getWorkouts() { 11 | return workouts; 12 | } 13 | 14 | public void addWorkout(ScheduledWorkout workout) { 15 | workouts.add(workout); 16 | } 17 | 18 | public void add(ScheduledWorkout workout) { 19 | workouts.add(workout); 20 | } 21 | 22 | @Override 23 | public String toString() { 24 | return "Schedule [workouts=" + workouts + "]"; 25 | } 26 | 27 | public LocalDate getRaceDate() { 28 | LocalDate result = null; 29 | 30 | for (ScheduledWorkout workout : workouts) { 31 | if ((result == null) || (workout.getDate().compareTo(result) > 0)) { 32 | result = workout.getDate(); 33 | } 34 | } 35 | return result; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/TimePaceStepConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.app.garmin.workout.TimePaceWorkoutStep; 4 | import com.johnpickup.app.garmin.workout.WorkoutStep; 5 | import com.johnpickup.garmin.common.unit.PaceTarget; 6 | import com.johnpickup.garmin.common.unit.Time; 7 | import com.johnpickup.garmin.parser.Step; 8 | import com.johnpickup.garmin.parser.TimePaceStep; 9 | 10 | /** 11 | * Convert independent pace steps into the Garmin equivalent 12 | */ 13 | public class TimePaceStepConverter implements StepConverter { 14 | @Override 15 | public WorkoutStep convert(Step step) { 16 | TimePaceStep timePaceStep = (TimePaceStep)step; 17 | 18 | Time t = new Time(timePaceStep.getTime().asDouble() * 60); 19 | PaceTarget p = PaceConverterFactory.getInstance().getPaceConverter(timePaceStep.getPace()).convert(timePaceStep.getPace()); 20 | TimePaceWorkoutStep timePaceWorkoutStep = new TimePaceWorkoutStep(StepIntensityConverter.convert(step.getStepIntensity()), t, p); 21 | return timePaceWorkoutStep; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/TimePowerStepConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.app.garmin.workout.TimePowerWorkoutStep; 4 | import com.johnpickup.app.garmin.workout.WorkoutStep; 5 | import com.johnpickup.garmin.common.unit.PowerTarget; 6 | import com.johnpickup.garmin.common.unit.Time; 7 | import com.johnpickup.garmin.parser.Step; 8 | import com.johnpickup.garmin.parser.TimePowerStep; 9 | 10 | /** 11 | * Convert independent pace steps into the Garmin equivalent 12 | */ 13 | public class TimePowerStepConverter implements StepConverter { 14 | @Override 15 | public WorkoutStep convert(Step step) { 16 | TimePowerStep timePowerStep = (TimePowerStep)step; 17 | 18 | Time t = new Time(timePowerStep.getTime().asDouble() * 60); 19 | 20 | PowerTarget powerTarget = PowerConverterFactory.getInstance() 21 | .getPowerConverter(timePowerStep.getPower()) 22 | .convert(timePowerStep.getPower()); 23 | 24 | return new TimePowerWorkoutStep(StepIntensityConverter.convert(step.getStepIntensity()), t, powerTarget); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/calendar/WorkoutScheduleConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.calendar; 2 | 3 | import com.johnpickup.garmin.parser.ScheduledWorkout; 4 | import com.johnpickup.garmin.parser.WorkoutSchedule; 5 | 6 | import java.time.LocalDate; 7 | 8 | public class WorkoutScheduleConverter { 9 | public Schedule convert(WorkoutSchedule workoutSchedule) { 10 | Schedule result = new Schedule(); 11 | 12 | for (ScheduledWorkout scheduledWorkout : workoutSchedule.getSchedule()) { 13 | com.johnpickup.app.calendar.ScheduledWorkout calendarScheduledWorkout = toCalendarScheduledWorkout(scheduledWorkout); 14 | result.add(calendarScheduledWorkout); 15 | 16 | } 17 | return result; 18 | } 19 | 20 | private com.johnpickup.app.calendar.ScheduledWorkout toCalendarScheduledWorkout(ScheduledWorkout scheduledWorkout) { 21 | String name = scheduledWorkout.getName(); 22 | String description = scheduledWorkout.getDescription(); 23 | LocalDate date = scheduledWorkout.getDate(); 24 | return new com.johnpickup.app.calendar.ScheduledWorkout(name, description, date); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/OpenHeartRateStepConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.app.garmin.workout.OpenHeartRateWorkoutStep; 4 | import com.johnpickup.app.garmin.workout.WorkoutStep; 5 | import com.johnpickup.garmin.common.unit.HeartRateTarget; 6 | import com.johnpickup.garmin.parser.OpenHeartRateStep; 7 | import com.johnpickup.garmin.parser.Step; 8 | 9 | /** 10 | * Convert independent pace steps into the Garmin equivalent 11 | */ 12 | public class OpenHeartRateStepConverter implements StepConverter { 13 | @Override 14 | public WorkoutStep convert(Step step) { 15 | OpenHeartRateStep openHeartRateStep = (OpenHeartRateStep)step; 16 | 17 | HeartRateTarget heartRateTarget = HeartRateConverterFactory.getInstance() 18 | .getHeartRateConverter(openHeartRateStep.getHeartRate()) 19 | .convert(openHeartRateStep.getHeartRate()); 20 | 21 | OpenHeartRateWorkoutStep openHeartRateWorkoutStep = 22 | new OpenHeartRateWorkoutStep(StepIntensityConverter.convert(step.getStepIntensity()), heartRateTarget); 23 | return openHeartRateWorkoutStep; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/RepeatingStepsConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.app.garmin.workout.RepeatingStepsWorkoutStep; 4 | import com.johnpickup.app.garmin.workout.WorkoutStep; 5 | import com.johnpickup.garmin.parser.RepeatingSteps; 6 | import com.johnpickup.garmin.parser.Step; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * Created by john on 09/01/2017. 13 | */ 14 | public class RepeatingStepsConverter implements StepConverter { 15 | @Override 16 | public WorkoutStep convert(Step step) { 17 | RepeatingSteps repeatingSteps = (RepeatingSteps)step; 18 | int repeatCount = repeatingSteps.getRepetitions(); 19 | List steps = new ArrayList<>(); 20 | for (Step internalStep : repeatingSteps.getSteps()) { 21 | steps.add(StepConverterFactory.getInstance().createFor(internalStep).convert(internalStep)); 22 | } 23 | RepeatingStepsWorkoutStep result = 24 | new RepeatingStepsWorkoutStep(StepIntensityConverter.convert(step.getStepIntensity()), repeatCount, steps); 25 | return result; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | --- The Following Works! --- 2 | Taken from https://centerkey.com/mac/java/ 3 | 4 | Using the fatjar, that already works when started with java -jar GarminToolsFull.jar 5 | 6 | 7 | cp ./resources/garmin128.png ./GarminTools.png 8 | sips -z 100 100 -p 150 150 GarminTools.png --out GarminTools-background.png 9 | mkdir GarminTools.iconset 10 | sips -z 128 128 GarminTools.png --out GarminTools.iconset/icon_128x128.png 11 | iconutil --convert icns GarminTools.iconset 12 | 13 | cp -v *.png *.icns package/macosx 14 | jdk=$(/usr/libexec/java_home) 15 | 16 | $jdk/bin/javapackager -deploy -native pkg -name GarminTools \ 17 | -BappVersion=1.0.0 -Bicon=package/macosx/GarminTools.icns \ 18 | -srcdir . -srcfiles GarminToolsFull.jar -appclass com.simontuffs.onejar.Boot \ 19 | -outdir out -v -outfile GarminTools.pkg 20 | 21 | 22 | Problems: The main menu shows the name as "About Boot". That is true with the fatjar, so not the fault of the installer 23 | 24 | Fatjar is currently failing to load the icon file (needed to add File.canRead() to get it to start) - a fatjar problem 25 | 26 | 27 | Maybe need to experiment with not using the fatjar, but just a normal jar 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/garmin/workout/OpenWorkoutStep.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.garmin.workout; 2 | 3 | import com.garmin.fit.Intensity; 4 | import com.garmin.fit.WktStepDuration; 5 | import com.garmin.fit.WktStepTarget; 6 | import com.garmin.fit.WorkoutStepMesg; 7 | 8 | import java.util.Collections; 9 | import java.util.List; 10 | 11 | /** 12 | * Simple workout the lasts a specific distance, no pace targets 13 | */ 14 | public class OpenWorkoutStep extends WorkoutStep { 15 | public OpenWorkoutStep(Intensity intensity) { 16 | super(intensity); 17 | } 18 | 19 | @Override 20 | public String getName() { 21 | return "Open"; 22 | } 23 | 24 | @Override 25 | public List generateWorkoutSteps() { 26 | WorkoutStepMesg step = new WorkoutStepMesg(); 27 | step.setIntensity(intensity); 28 | step.setDurationType(WktStepDuration.OPEN); 29 | step.setTargetType(WktStepTarget.OPEN); 30 | step.setMessageIndex(generateWorkoutStepIndex()); 31 | step.setTargetValue(0L); 32 | step.setNotes(nameWithIntensity()); 33 | 34 | return Collections.singletonList(step); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/parser/PaceRangeTextParser.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.parser; 2 | 3 | import com.johnpickup.garmin.parser.Pace; 4 | import com.johnpickup.garmin.parser.WorkoutLexer; 5 | import com.johnpickup.garmin.parser.WorkoutParser; 6 | import org.antlr.v4.runtime.CharStream; 7 | import org.antlr.v4.runtime.CharStreams; 8 | import org.antlr.v4.runtime.CommonTokenStream; 9 | import org.antlr.v4.runtime.TokenStream; 10 | 11 | import java.io.ByteArrayInputStream; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | 15 | 16 | /** 17 | * Created by john on 03/01/2017. 18 | */ 19 | public class PaceRangeTextParser { 20 | public Pace parse(String inputText) { 21 | try { 22 | InputStream input = new ByteArrayInputStream(inputText.getBytes()); 23 | CharStream charStream = CharStreams.fromStream(input); 24 | WorkoutLexer lexer = new WorkoutLexer(charStream); 25 | TokenStream tokens = new CommonTokenStream(lexer); 26 | WorkoutParser parser = new WorkoutParser(tokens); 27 | return parser.pace_range().value; 28 | } 29 | catch (IOException ex) { 30 | throw new RuntimeException(ex); 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/PaceLimit.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * Created by john on 03/01/2017. 7 | */ 8 | public class PaceLimit implements Pace { 9 | private final Time time; 10 | private final PaceUnit unit; 11 | 12 | public PaceLimit(Time time, PaceUnit unit) { 13 | this.time = time; 14 | this.unit = unit; 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return String.format("%s%s", getTime(), getUnit()); 20 | } 21 | 22 | @Override 23 | public boolean equals(Object o) { 24 | if (this == o) return true; 25 | if (o == null || getClass() != o.getClass()) return false; 26 | PaceLimit paceLimit = (PaceLimit) o; 27 | return Objects.equals(time, paceLimit.time) && unit == paceLimit.unit; 28 | } 29 | 30 | @Override 31 | public int hashCode() { 32 | return Objects.hash(time, unit); 33 | } 34 | 35 | protected boolean canEqual(final Object other) { 36 | return other instanceof PaceLimit; 37 | } 38 | 39 | public Time getTime() { 40 | return this.time; 41 | } 42 | 43 | public PaceUnit getUnit() { 44 | return this.unit; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/OpenPaceStep.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.util.Objects; 4 | 5 | public class OpenPaceStep extends Step { 6 | private final Pace pace; 7 | 8 | public OpenPaceStep(Pace pace) { 9 | super(); 10 | this.pace = pace; 11 | } 12 | 13 | public OpenPaceStep(StepIntensity stepIntensity, Pace pace) { 14 | super(stepIntensity); 15 | this.pace = pace; 16 | } 17 | @Override 18 | public String toString() { 19 | return "Open@" + pace + (stepIntensity==null?"":("|"+stepIntensity)); 20 | } 21 | 22 | @Override 23 | public boolean equals(Object o) { 24 | if (this == o) return true; 25 | if (o == null || getClass() != o.getClass()) return false; 26 | OpenPaceStep that = (OpenPaceStep) o; 27 | return Objects.equals(pace, that.pace) 28 | && Objects.equals(stepIntensity, that.stepIntensity); 29 | } 30 | 31 | @Override 32 | public int hashCode() { 33 | return Objects.hash(pace); 34 | } 35 | 36 | protected boolean canEqual(final Object other) { 37 | return other instanceof OpenPaceStep; 38 | } 39 | 40 | public Pace getPace() { 41 | return this.pace; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/PowerConverterFactory.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.garmin.parser.Power; 4 | import com.johnpickup.garmin.parser.PowerRange; 5 | import com.johnpickup.garmin.parser.PowerZone; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * Factory that returns the correct converter instance based on the type of heart rate target object passed in 12 | */ 13 | public class PowerConverterFactory { 14 | private static PowerConverterFactory instance; 15 | private final Map converters = new HashMap<>(); 16 | 17 | private PowerConverterFactory() { 18 | register(new ZonePowerConverter(), PowerZone.class); 19 | register(new CustomPowerConverter(), PowerRange.class); 20 | } 21 | 22 | public void register(PowerConverter converter, Class aClass) { 23 | converters.put(aClass, converter); 24 | } 25 | 26 | public static PowerConverterFactory getInstance() { 27 | if (instance == null) { 28 | instance = new PowerConverterFactory(); 29 | } 30 | return instance; 31 | } 32 | 33 | public PowerConverter getPowerConverter(Power power) { 34 | return converters.get(power.getClass()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/DistancePowerStepConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.app.garmin.workout.DistancePowerWorkoutStep; 4 | import com.johnpickup.app.garmin.workout.WorkoutStep; 5 | import com.johnpickup.garmin.common.unit.Distance; 6 | import com.johnpickup.garmin.common.unit.PowerTarget; 7 | import com.johnpickup.garmin.parser.DistancePowerStep; 8 | import com.johnpickup.garmin.parser.Step; 9 | 10 | /** 11 | * Convert independent pace steps into the Garmin equivalent 12 | */ 13 | public class DistancePowerStepConverter implements StepConverter { 14 | @Override 15 | public WorkoutStep convert(Step step) { 16 | DistancePowerStep distancePowerStep = (DistancePowerStep)step; 17 | 18 | Distance d = new Distance( 19 | distancePowerStep.getDistance().getQuantity(), 20 | DiatanceUnitConverter.convert(distancePowerStep.getDistance().getUnit())); 21 | 22 | PowerTarget powerTarget = PowerConverterFactory.getInstance() 23 | .getPowerConverter(distancePowerStep.getPower()) 24 | .convert(distancePowerStep.getPower()); 25 | 26 | return new DistancePowerWorkoutStep(StepIntensityConverter.convert(step.getStepIntensity()), d, powerTarget); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/test/java/com/johnpickup/app/parser/TimeTest.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.parser; 2 | 3 | import com.johnpickup.garmin.parser.Time; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.assertEquals; 7 | 8 | /** 9 | * Test time string parsing and toString 10 | */ 11 | public class TimeTest { 12 | private static final double EPSILON = 1e-4; 13 | @Test 14 | public void parseTimeZeroSeconds() throws Exception { 15 | double actual = Time.parseTime("08:00").asDouble(); 16 | double expected = 8.0; 17 | assertEquals(expected, actual, EPSILON); 18 | } 19 | 20 | @Test 21 | public void parseTimeThirtySeconds() throws Exception { 22 | double actual = Time.parseTime("12:30").asDouble(); 23 | double expected = 12.5; 24 | assertEquals(expected, actual, EPSILON); 25 | } 26 | 27 | @Test 28 | public void testToString() throws Exception { 29 | String actual = (new Time(10,30)).toString(); 30 | String expected = "10:30"; 31 | assertEquals(expected, actual); 32 | } 33 | 34 | @Test 35 | public void testToStringZeroMinutes() throws Exception { 36 | String actual = (new Time(1,0)).toString(); 37 | String expected = "1:00"; 38 | assertEquals(expected, actual); 39 | } 40 | } -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/Distance.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * Created by john on 03/01/2017. 7 | */ 8 | public class Distance { 9 | private final double quantity; 10 | private final DistanceUnit unit; 11 | 12 | public Distance(double quantity, DistanceUnit unit) { 13 | this.quantity = quantity; 14 | this.unit = unit; 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return Double.toString(quantity) + unit; 20 | } 21 | 22 | @Override 23 | public boolean equals(Object o) { 24 | if (this == o) return true; 25 | if (o == null || getClass() != o.getClass()) return false; 26 | Distance distance = (Distance) o; 27 | return Double.compare(distance.quantity, quantity) == 0 && unit == distance.unit; 28 | } 29 | 30 | @Override 31 | public int hashCode() { 32 | return Objects.hash(quantity, unit); 33 | } 34 | 35 | protected boolean canEqual(final Object other) { 36 | return other instanceof Distance; 37 | } 38 | 39 | public double getQuantity() { 40 | return this.quantity; 41 | } 42 | 43 | public DistanceUnit getUnit() { 44 | return this.unit; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GarminTools 2 | A suite of tools for creating Garmin FIT files for workouts, schedules and courses 3 | 4 | [Workouts and Schedules](./WORKOUTS.md) 5 | 6 | [Routes](./ROUTES.md) 7 | 8 | ## Building 9 | The application is a multi-module Maven project. It uses jlink and jpackage to produce installers for Windows, MacOS and Linux. 10 | 11 | The resulting installer files can be found in `app/target/installer` after the maven build has completed on the target platform. 12 | 13 | The main class is `app/src/main/java/com/johnpickup/app/javafx/AppLauncher.java` if you want to run this from an IDE. 14 | 15 | ## Installers 16 | The project now uses GitHub actions to build Windows and Linux installations. These can be found in the project's packages, 17 | here: https://github.com/jpickup/GarminTools/packages/ 18 | Click on `com.johnpickup.garmin.bundle` and search for "windows.zip", "linux.zip" or "macos.zip", download and unpack. 19 | I need to figure out a better way to link to these. 20 | 21 | Previously the installers were checked directly into GitHub but once the size exceeded the GitHub limit this was no longer 22 | suitable. 23 | 24 | NOTE: 25 | For Windows the most recent version is available in a ZIP file as I've been unable to get compatible versions of 26 | the various tools required to build an MSI. 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/DistancePaceStepConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.app.garmin.workout.DistancePaceWorkoutStep; 4 | import com.johnpickup.app.garmin.workout.WorkoutStep; 5 | import com.johnpickup.garmin.common.unit.Distance; 6 | import com.johnpickup.garmin.common.unit.PaceTarget; 7 | import com.johnpickup.garmin.parser.DistancePaceStep; 8 | import com.johnpickup.garmin.parser.Step; 9 | 10 | /** 11 | * Convert independent pace steps into the Garmin equivalent 12 | */ 13 | public class DistancePaceStepConverter implements StepConverter { 14 | @Override 15 | public WorkoutStep convert(Step step) { 16 | DistancePaceStep distancePaceStep = (DistancePaceStep)step; 17 | 18 | Distance d = new Distance( 19 | distancePaceStep.getDistance().getQuantity(), 20 | DiatanceUnitConverter.convert(distancePaceStep.getDistance().getUnit())); 21 | PaceTarget p = PaceConverterFactory.getInstance().getPaceConverter(distancePaceStep.getPace()).convert(distancePaceStep.getPace()); 22 | DistancePaceWorkoutStep distancePaceWorkoutStep = 23 | new DistancePaceWorkoutStep(StepIntensityConverter.convert(step.getStepIntensity()), d, p); 24 | return distancePaceWorkoutStep; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/TimeHeartRateStepConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.app.garmin.workout.TimeHeartRateWorkoutStep; 4 | import com.johnpickup.app.garmin.workout.WorkoutStep; 5 | import com.johnpickup.garmin.common.unit.HeartRateTarget; 6 | import com.johnpickup.garmin.common.unit.Time; 7 | import com.johnpickup.garmin.parser.Step; 8 | import com.johnpickup.garmin.parser.TimeHeartRateStep; 9 | 10 | /** 11 | * Convert independent pace steps into the Garmin equivalent 12 | */ 13 | public class TimeHeartRateStepConverter implements StepConverter { 14 | @Override 15 | public WorkoutStep convert(Step step) { 16 | TimeHeartRateStep timeHeartRateStep = (TimeHeartRateStep)step; 17 | 18 | Time t = new Time(timeHeartRateStep.getTime().asDouble() * 60); 19 | 20 | HeartRateTarget heartRateTarget = HeartRateConverterFactory.getInstance() 21 | .getHeartRateConverter(timeHeartRateStep.getHeartRate()) 22 | .convert(timeHeartRateStep.getHeartRate()); 23 | 24 | TimeHeartRateWorkoutStep timeHeartRateWorkoutStep = 25 | new TimeHeartRateWorkoutStep(StepIntensityConverter.convert(step.getStepIntensity()), t, heartRateTarget); 26 | return timeHeartRateWorkoutStep; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/OpenPowerStep.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.util.Objects; 4 | 5 | public class OpenPowerStep extends Step { 6 | private final Power power; 7 | 8 | public OpenPowerStep(Power power) { 9 | super(); 10 | this.power = power; 11 | } 12 | 13 | public OpenPowerStep(StepIntensity stepIntensity, Power power) { 14 | super(stepIntensity); 15 | this.power = power; 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return "Open@" + power + (stepIntensity==null?"":("|"+stepIntensity)); 21 | } 22 | 23 | @Override 24 | public boolean equals(Object o) { 25 | if (this == o) return true; 26 | if (o == null || getClass() != o.getClass()) return false; 27 | OpenPowerStep that = (OpenPowerStep) o; 28 | return Objects.equals(power, that.power) 29 | && Objects.equals(stepIntensity, that.stepIntensity); 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(power); 35 | } 36 | 37 | protected boolean canEqual(final Object other) { 38 | return other instanceof OpenPowerStep; 39 | } 40 | 41 | public Power getPower() { 42 | return this.power; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /.github/workflows/maven-publish-linux.yml: -------------------------------------------------------------------------------- 1 | name: Maven Package for Linux 2 | 3 | #on: 4 | # release: 5 | # types: [created] 6 | on: [push] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: read 13 | packages: write 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Set up JDK 23 18 | uses: actions/setup-java@v4 19 | with: 20 | java-version: '23' 21 | distribution: 'oracle' 22 | server-id: github # Value of the distributionManagement/repository/id field of the pom.xml 23 | settings-path: ${{ github.workspace }} # location for the settings.xml file 24 | 25 | - name: Build with Maven 26 | run: mvn install -s $GITHUB_WORKSPACE/settings.xml 27 | env: 28 | GITHUB_TOKEN: ${{ github.token }} 29 | 30 | # this is nasty! the publish fails as the pom already exists but we only really want to artefact published, so we try to ignore the error 31 | # this is also why the full build is performed in the previous step as we do want that to fail if there are other errors in the build 32 | - name: Publish to GitHub Packages Apache Maven 33 | run: mvn deploy -s $GITHUB_WORKSPACE/settings.xml || echo "Ignoring non-zero result" 34 | env: 35 | GITHUB_TOKEN: ${{ github.token }} 36 | -------------------------------------------------------------------------------- /.github/workflows/maven-publish-windows.yml: -------------------------------------------------------------------------------- 1 | name: Maven Package for Windows 2 | 3 | #on: 4 | # release: 5 | # types: [created] 6 | on: [push] 7 | 8 | jobs: 9 | build: 10 | runs-on: windows-latest 11 | permissions: 12 | contents: read 13 | packages: write 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Set up JDK 23 18 | uses: actions/setup-java@v4 19 | with: 20 | java-version: '23' 21 | distribution: 'oracle' 22 | server-id: github # Value of the distributionManagement/repository/id field of the pom.xml 23 | settings-path: ${{ github.workspace }} # location for the settings.xml file 24 | 25 | - name: Build with Maven 26 | run: mvn install -s ${{ github.workspace }}\settings.xml --file pom.xml 27 | 28 | # this is nasty! the publish fails as the pom already exists but we only really want to artefact published, so we try to ignore the error 29 | # this is also why the full build is performed in the previous step as we do want that to fail if there are other errors in the build 30 | - name: Publish to GitHub Packages Apache Maven 31 | run: mvn deploy -s ${{ github.workspace }}\settings.xml --file pom.xml || echo "Ignoring non-zero result" || exit 0 32 | env: 33 | GITHUB_TOKEN: ${{ github.token }} 34 | 35 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/TimeStep.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * Created by john on 03/01/2017. 7 | */ 8 | public class TimeStep extends Step { 9 | private final Time time; 10 | 11 | public TimeStep(Time time) { 12 | super(); 13 | this.time = time; 14 | } 15 | 16 | public TimeStep(StepIntensity stepIntensity, Time time) { 17 | super(stepIntensity); 18 | this.time = time; 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return time.toString() + (stepIntensity==null?"":("|"+stepIntensity)); 24 | } 25 | 26 | @Override 27 | public boolean equals(Object o) { 28 | if (this == o) return true; 29 | if (o == null || getClass() != o.getClass()) return false; 30 | TimeStep timeStep = (TimeStep) o; 31 | return Objects.equals(time, timeStep.time) 32 | && Objects.equals(stepIntensity, timeStep.stepIntensity); 33 | } 34 | 35 | @Override 36 | public int hashCode() { 37 | return Objects.hash(time); 38 | } 39 | 40 | protected boolean canEqual(final Object other) { 41 | return other instanceof TimeStep; 42 | } 43 | 44 | public Time getTime() { 45 | return this.time; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/HeartRateConverterFactory.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.garmin.parser.HeartRate; 4 | import com.johnpickup.garmin.parser.HeartRateRange; 5 | import com.johnpickup.garmin.parser.HeartRateZone; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * Factory that returns the correct converter instance based on the type of heart rate target object passed in 12 | */ 13 | public class HeartRateConverterFactory { 14 | private static HeartRateConverterFactory instance; 15 | private Map converters = new HashMap<>(); 16 | 17 | private HeartRateConverterFactory() { 18 | register(new ZoneHeartRateConverter(), HeartRateZone.class); 19 | register(new CustomHeartRateConverter(), HeartRateRange.class); 20 | } 21 | 22 | public void register(HeartRateConverter converter, Class aClass) { 23 | converters.put(aClass, converter); 24 | } 25 | 26 | public static HeartRateConverterFactory getInstance() { 27 | if (instance == null) { 28 | instance = new HeartRateConverterFactory(); 29 | } 30 | return instance; 31 | } 32 | 33 | public HeartRateConverter getHeartRateConverter(HeartRate heartRate) { 34 | return converters.get(heartRate.getClass()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/PaceConverterFactory.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.garmin.parser.MaximumPace; 4 | import com.johnpickup.garmin.parser.MinimumPace; 5 | import com.johnpickup.garmin.parser.Pace; 6 | import com.johnpickup.garmin.parser.PaceRange; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | /** 12 | * Factory that returns the correct converter instance based on the type of pace object passed in 13 | */ 14 | public class PaceConverterFactory { 15 | private static PaceConverterFactory instance; 16 | private Map converters = new HashMap<>(); 17 | 18 | private PaceConverterFactory() { 19 | register(new PaceRangeConverter(), PaceRange.class); 20 | register(new MaximumPaceConverter(), MaximumPace.class); 21 | register(new MinimumPaceConverter(), MinimumPace.class); 22 | } 23 | 24 | public void register(PaceConverter converter, Class aClass) { 25 | converters.put(aClass, converter); 26 | } 27 | 28 | public static PaceConverterFactory getInstance() { 29 | if (instance == null) { 30 | instance = new PaceConverterFactory(); 31 | } 32 | return instance; 33 | } 34 | 35 | public PaceConverter getPaceConverter(Pace pace) { 36 | return converters.get(pace.getClass()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/garmin/schedule/ScheduledWorkout.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.garmin.schedule; 2 | 3 | import com.garmin.fit.DateTime; 4 | import com.johnpickup.app.garmin.workout.Workout; 5 | 6 | import java.time.LocalDate; 7 | import java.time.LocalTime; 8 | import java.time.ZoneId; 9 | import java.time.ZonedDateTime; 10 | import java.util.Date; 11 | 12 | /** 13 | * Created by john on 31/12/2016. 14 | */ 15 | public class ScheduledWorkout { 16 | private final Workout workout; 17 | private final LocalDate date; 18 | 19 | public ScheduledWorkout(Workout workout, LocalDate date) { 20 | this.workout = workout; 21 | this.date = date; 22 | } 23 | 24 | /** Get the scheduled time as a Garmin time long */ 25 | public Long getGarminTime() { 26 | ZonedDateTime zdt = ZonedDateTime.of(date, LocalTime.MIDNIGHT, ZoneId.of("UTC")); 27 | DateTime dateTime = new DateTime(Date.from(zdt.toInstant())); 28 | return dateTime.getTimestamp(); 29 | } 30 | 31 | public Workout getWorkout() { 32 | return this.workout; 33 | } 34 | 35 | public LocalDate getDate() { 36 | return this.date; 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return "ScheduledWorkout{" + 42 | "workout=" + workout + 43 | ", date=" + date + 44 | '}'; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/DistanceStep.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * Created by john on 03/01/2017. 7 | */ 8 | public class DistanceStep extends Step { 9 | private final Distance distance; 10 | 11 | public DistanceStep(Distance distance) { 12 | super(); 13 | this.distance = distance; 14 | } 15 | 16 | public DistanceStep(StepIntensity stepIntensity, Distance distance) { 17 | super(stepIntensity); 18 | this.distance = distance; 19 | } 20 | @Override 21 | public String toString() { 22 | return distance.toString() + (stepIntensity==null?"":("|"+stepIntensity)); 23 | } 24 | 25 | @Override 26 | public boolean equals(Object o) { 27 | if (this == o) return true; 28 | if (o == null || getClass() != o.getClass()) return false; 29 | DistanceStep that = (DistanceStep) o; 30 | return Objects.equals(distance, that.distance) 31 | && Objects.equals(stepIntensity, that.stepIntensity); 32 | } 33 | 34 | @Override 35 | public int hashCode() { 36 | return Objects.hash(distance); 37 | } 38 | 39 | protected boolean canEqual(final Object other) { 40 | return other instanceof DistanceStep; 41 | } 42 | 43 | public Distance getDistance() { 44 | return this.distance; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.johnpickup.garmin 8 | GarminTools 9 | 1.3 10 | 11 | 12 | common 13 | 14 | 15 | ${java.version} 16 | ${java.version} 17 | UTF-8 18 | true 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | org.apache.maven.plugins 28 | maven-compiler-plugin 29 | 3.10.1 30 | 31 | ${java.version} 32 | ${java.version} 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/PowerRange.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.util.Objects; 4 | 5 | public class PowerRange implements Power { 6 | private final int minimum; 7 | private final int maximum; 8 | private final PowerUnit unit; 9 | 10 | public PowerRange(int minimum, int maximum, PowerUnit unit) { 11 | this.minimum = minimum; 12 | this.maximum = maximum; 13 | this.unit = unit; 14 | } 15 | 16 | @Override 17 | public String toString() { 18 | return String.format("%s-%s%s", minimum, maximum, unit); 19 | } 20 | 21 | @Override 22 | public boolean equals(Object o) { 23 | if (this == o) return true; 24 | if (o == null || getClass() != o.getClass()) return false; 25 | PowerRange that = (PowerRange) o; 26 | return minimum == that.minimum && maximum == that.maximum && unit == that.unit; 27 | } 28 | 29 | @Override 30 | public int hashCode() { 31 | return Objects.hash(minimum, maximum, unit); 32 | } 33 | 34 | protected boolean canEqual(final Object other) { 35 | return other instanceof PowerRange; 36 | } 37 | 38 | public int getMinimum() { 39 | return this.minimum; 40 | } 41 | 42 | public int getMaximum() { 43 | return this.maximum; 44 | } 45 | 46 | public PowerUnit getUnit() { 47 | return this.unit; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /common/src/main/java/com/johnpickup/garmin/common/unit/Power.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.common.unit; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * Encapsulation of custom power value with human-readable toString plus a conversion to Garmin units 7 | */ 8 | public class Power { 9 | private final long value; 10 | private final PowerUnit unit; 11 | 12 | public Power(long value, PowerUnit unit) { 13 | this.value = value; 14 | this.unit = unit; 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return String.format("%s%s", toValueString(), unit.getShortName()); 20 | } 21 | 22 | public Long toGarminPower() { 23 | // Garmin power units are in watts offset by 1000 24 | // 0 – 1000% reserved for functional threshold power (FTP) 25 | return switch (unit) { 26 | case WATTS -> value + 1000; 27 | }; 28 | } 29 | 30 | public String toValueString() { 31 | return switch (unit) { 32 | case WATTS -> String.format("%d", value); 33 | }; 34 | } 35 | 36 | @Override 37 | public boolean equals(Object o) { 38 | if (o == null || getClass() != o.getClass()) return false; 39 | Power power = (Power) o; 40 | return value == power.value && unit == power.unit; 41 | } 42 | 43 | @Override 44 | public int hashCode() { 45 | return Objects.hash(value, unit); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/OpenHeartRateStep.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * Created by john on 03/01/2017. 7 | */ 8 | public class OpenHeartRateStep extends Step { 9 | private final HeartRate heartRate; 10 | 11 | public OpenHeartRateStep(HeartRate heartRate) { 12 | super(); 13 | this.heartRate = heartRate; 14 | } 15 | 16 | public OpenHeartRateStep(StepIntensity stepIntensity, HeartRate heartRate) { 17 | super(stepIntensity); 18 | this.heartRate = heartRate; 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return "Open@" + heartRate + (stepIntensity==null?"":("|"+stepIntensity)); 24 | } 25 | 26 | @Override 27 | public boolean equals(Object o) { 28 | if (this == o) return true; 29 | if (o == null || getClass() != o.getClass()) return false; 30 | OpenHeartRateStep that = (OpenHeartRateStep) o; 31 | return Objects.equals(heartRate, that.heartRate) 32 | && Objects.equals(stepIntensity, that.stepIntensity); 33 | } 34 | 35 | @Override 36 | public int hashCode() { 37 | return Objects.hash(heartRate); 38 | } 39 | 40 | protected boolean canEqual(final Object other) { 41 | return other instanceof OpenHeartRateStep; 42 | } 43 | 44 | public HeartRate getHeartRate() { 45 | return this.heartRate; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/HeartRateRange.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.util.Objects; 4 | 5 | public class HeartRateRange implements HeartRate { 6 | private final int minimum; 7 | private final int maximum; 8 | private final HeartRateUnit unit; 9 | 10 | public HeartRateRange(int minimum, int maximum, HeartRateUnit unit) { 11 | this.minimum = minimum; 12 | this.maximum = maximum; 13 | this.unit = unit; 14 | } 15 | 16 | @Override 17 | public String toString() { 18 | return String.format("%s-%s%s", minimum, maximum, unit); 19 | } 20 | 21 | @Override 22 | public boolean equals(Object o) { 23 | if (this == o) return true; 24 | if (o == null || getClass() != o.getClass()) return false; 25 | HeartRateRange that = (HeartRateRange) o; 26 | return minimum == that.minimum && maximum == that.maximum && unit == that.unit; 27 | } 28 | 29 | @Override 30 | public int hashCode() { 31 | return Objects.hash(minimum, maximum, unit); 32 | } 33 | 34 | protected boolean canEqual(final Object other) { 35 | return other instanceof HeartRateRange; 36 | } 37 | 38 | public int getMinimum() { 39 | return this.minimum; 40 | } 41 | 42 | public int getMaximum() { 43 | return this.maximum; 44 | } 45 | 46 | public HeartRateUnit getUnit() { 47 | return this.unit; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /common/src/main/java/com/johnpickup/garmin/common/unit/Distance.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.common.unit; 2 | 3 | import java.util.Objects; 4 | 5 | public class Distance { 6 | private final double value; 7 | private final DistanceUnit unit; 8 | 9 | public Distance(double value, DistanceUnit unit) { 10 | this.value = value; 11 | this.unit = unit; 12 | } 13 | 14 | @Override 15 | public String toString() { 16 | if(value == (long) value) 17 | return String.format("%d%s",(long)value, unit.getShortName()); 18 | else 19 | return String.format("%s%s", value, unit.getShortName()); 20 | } 21 | 22 | public Float toGarminDistance() { 23 | return switch (unit) { 24 | case METRE -> (float) (value); 25 | case KILOMETRE -> (float) (value * 1000F); 26 | case MILE -> (float) (value * 1609F); 27 | }; 28 | } 29 | 30 | @Override 31 | public boolean equals(Object o) { 32 | if (this == o) return true; 33 | if (o == null || getClass() != o.getClass()) return false; 34 | Distance distance = (Distance) o; 35 | return Double.compare(distance.value, value) == 0 && unit == distance.unit; 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | return Objects.hash(value, unit); 41 | } 42 | 43 | protected boolean canEqual(final Object other) { 44 | return other instanceof Distance; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/DistanceHeartRateStepConverter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.app.garmin.workout.DistanceHeartRateWorkoutStep; 4 | import com.johnpickup.app.garmin.workout.WorkoutStep; 5 | import com.johnpickup.garmin.common.unit.Distance; 6 | import com.johnpickup.garmin.common.unit.HeartRateTarget; 7 | import com.johnpickup.garmin.parser.DistanceHeartRateStep; 8 | import com.johnpickup.garmin.parser.Step; 9 | 10 | /** 11 | * Convert independent pace steps into the Garmin equivalent 12 | */ 13 | public class DistanceHeartRateStepConverter implements StepConverter { 14 | @Override 15 | public WorkoutStep convert(Step step) { 16 | DistanceHeartRateStep distanceHeartRateStep = (DistanceHeartRateStep)step; 17 | 18 | Distance d = new Distance( 19 | distanceHeartRateStep.getDistance().getQuantity(), 20 | DiatanceUnitConverter.convert(distanceHeartRateStep.getDistance().getUnit())); 21 | 22 | HeartRateTarget heartRateTarget = HeartRateConverterFactory.getInstance() 23 | .getHeartRateConverter(distanceHeartRateStep.getHeartRate()) 24 | .convert(distanceHeartRateStep.getHeartRate()); 25 | 26 | DistanceHeartRateWorkoutStep distanceHeartRateWorkoutStep = 27 | new DistanceHeartRateWorkoutStep(StepIntensityConverter.convert(step.getStepIntensity()), d, heartRateTarget); 28 | return distanceHeartRateWorkoutStep; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/PaceRange.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * Created by john on 03/01/2017. 7 | */ 8 | public class PaceRange implements Pace { 9 | private final Time minimum; 10 | private final Time maximum; 11 | private final PaceUnit unit; 12 | 13 | public PaceRange(Time minimum, Time maximum, PaceUnit unit) { 14 | this.minimum = minimum; 15 | this.maximum = maximum; 16 | this.unit = unit; 17 | } 18 | 19 | @Override 20 | public String toString() { 21 | return String.format("%s-%s%s",maximum, minimum, unit); 22 | } 23 | 24 | @Override 25 | public boolean equals(Object o) { 26 | if (this == o) return true; 27 | if (o == null || getClass() != o.getClass()) return false; 28 | PaceRange paceRange = (PaceRange) o; 29 | return Objects.equals(minimum, paceRange.minimum) && Objects.equals(maximum, paceRange.maximum) && unit == paceRange.unit; 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(minimum, maximum, unit); 35 | } 36 | 37 | protected boolean canEqual(final Object other) { 38 | return other instanceof PaceRange; 39 | } 40 | 41 | public Time getMinimum() { 42 | return this.minimum; 43 | } 44 | 45 | public Time getMaximum() { 46 | return this.maximum; 47 | } 48 | 49 | public PaceUnit getUnit() { 50 | return this.unit; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/calendar/PlannedWorkout.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.calendar; 2 | 3 | import java.util.Objects; 4 | 5 | public class PlannedWorkout { 6 | private final String name; 7 | private final String description; 8 | private final long offset; 9 | 10 | public PlannedWorkout(String name, String description, long offset) { 11 | this.name = name; 12 | this.description = description; 13 | this.offset = offset; 14 | } 15 | 16 | public String getName() { 17 | return this.name; 18 | } 19 | 20 | public String getDescription() { 21 | return this.description; 22 | } 23 | 24 | public long getOffset() { 25 | return this.offset; 26 | } 27 | 28 | @Override 29 | public boolean equals(Object o) { 30 | if (this == o) return true; 31 | if (o == null || getClass() != o.getClass()) return false; 32 | PlannedWorkout that = (PlannedWorkout) o; 33 | return offset == that.offset && Objects.equals(name, that.name) && Objects.equals(description, that.description); 34 | } 35 | 36 | @Override 37 | public int hashCode() { 38 | return Objects.hash(name, description, offset); 39 | } 40 | 41 | protected boolean canEqual(final Object other) { 42 | return other instanceof PlannedWorkout; 43 | } 44 | 45 | public String toString() { 46 | return "PlannedWorkout(name=" + this.getName() + ", description=" + this.getDescription() + ", offset=" + this.getOffset() + ")"; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/TimePowerStep.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.util.Objects; 4 | 5 | public class TimePowerStep extends Step { 6 | private final Time time; 7 | private final Power power; 8 | 9 | public TimePowerStep(Time time, Power power) { 10 | super(); 11 | this.time = time; 12 | this.power = power; 13 | } 14 | 15 | public TimePowerStep(StepIntensity stepIntensity, Time time, Power power) { 16 | super(stepIntensity); 17 | this.time = time; 18 | this.power = power; 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return time + "@" + power + (stepIntensity==null?"":("|"+stepIntensity)); 24 | } 25 | 26 | @Override 27 | public boolean equals(Object o) { 28 | if (this == o) return true; 29 | if (o == null || getClass() != o.getClass()) return false; 30 | TimePowerStep that = (TimePowerStep) o; 31 | return Objects.equals(time, that.time) && Objects.equals(power, that.power) 32 | && Objects.equals(stepIntensity, that.stepIntensity); 33 | } 34 | 35 | @Override 36 | public int hashCode() { 37 | return Objects.hash(time, power); 38 | } 39 | 40 | protected boolean canEqual(final Object other) { 41 | return other instanceof TimePowerStep; 42 | } 43 | 44 | public Time getTime() { 45 | return this.time; 46 | } 47 | 48 | public Power getPower() { 49 | return this.power; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/WorkoutSchedule.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.util.*; 4 | 5 | /** 6 | * Class that contains all the elements required to build a series of workouts and schedule them 7 | */ 8 | public class WorkoutSchedule { 9 | private final Map workouts = new HashMap<>(); 10 | private final Map paces = new HashMap<>(); 11 | private final List schedule = new ArrayList<>(); 12 | 13 | @Override 14 | public boolean equals(Object o) { 15 | if (this == o) return true; 16 | if (o == null || getClass() != o.getClass()) return false; 17 | WorkoutSchedule that = (WorkoutSchedule) o; 18 | return workouts.equals(that.workouts) && paces.equals(that.paces) && schedule.equals(that.schedule); 19 | } 20 | 21 | @Override 22 | public int hashCode() { 23 | return Objects.hash(workouts, paces, schedule); 24 | } 25 | 26 | protected boolean canEqual(final Object other) { 27 | return other instanceof WorkoutSchedule; 28 | } 29 | 30 | public String toString() { 31 | return "WorkoutSchedule(workouts=" + this.workouts + ", paces=" + this.paces + ", schedule=" + this.schedule + ")"; 32 | } 33 | 34 | public Map getWorkouts() { 35 | return this.workouts; 36 | } 37 | 38 | public Map getPaces() { 39 | return this.paces; 40 | } 41 | 42 | public List getSchedule() { 43 | return this.schedule; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/TimePaceStep.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * Created by john on 03/01/2017. 7 | */ 8 | public class TimePaceStep extends Step { 9 | private final Time time; 10 | private final Pace pace; 11 | 12 | public TimePaceStep(Time time, Pace pace) { 13 | super(); 14 | this.time = time; 15 | this.pace = pace; 16 | } 17 | 18 | public TimePaceStep(StepIntensity stepIntensity, Time time, Pace pace) { 19 | super(stepIntensity); 20 | this.time = time; 21 | this.pace = pace; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return time + "@" + pace + (stepIntensity==null?"":("|"+stepIntensity)); 27 | } 28 | 29 | @Override 30 | public boolean equals(Object o) { 31 | if (this == o) return true; 32 | if (o == null || getClass() != o.getClass()) return false; 33 | TimePaceStep that = (TimePaceStep) o; 34 | return Objects.equals(time, that.time) && Objects.equals(pace, that.pace) 35 | && Objects.equals(stepIntensity, that.stepIntensity); 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | return Objects.hash(time, pace); 41 | } 42 | 43 | protected boolean canEqual(final Object other) { 44 | return other instanceof TimePaceStep; 45 | } 46 | 47 | public Time getTime() { 48 | return this.time; 49 | } 50 | 51 | public Pace getPace() { 52 | return this.pace; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/javafx/ConversionType.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.javafx; 2 | 3 | import com.johnpickup.app.task.GpxTask; 4 | import com.johnpickup.app.task.ScheduleFitTask; 5 | import com.johnpickup.app.task.ScheduleIcalTask; 6 | import javafx.stage.FileChooser; 7 | 8 | import java.util.List; 9 | 10 | public enum ConversionType { 11 | GPX_TO_FIT("GPX to FIT", GpxTask.class, 12 | List.of(new FileChooser.ExtensionFilter("GPX", "*.gpx"))), 13 | SCHEDULE_TO_FIT("Excel Schedule to FIT", ScheduleFitTask.class, 14 | List.of(new FileChooser.ExtensionFilter("Excel Workbook", "*.xlsx", "*.xls"))), 15 | SCHEDULE_TO_ICAL("Excel Schedule to iCal", ScheduleIcalTask.class, 16 | List.of(new FileChooser.ExtensionFilter("Excel Workbook", "*.xlsx", "*.xls"))); 17 | private final String description; 18 | 19 | public Class getTask() { 20 | return task; 21 | } 22 | 23 | private final Class task; 24 | private final List filePattern; 25 | 26 | private ConversionType(String description, Class task, List filePattern) { 27 | this.description = description; 28 | this.task = task; 29 | this.filePattern = filePattern; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return description; 35 | } 36 | 37 | public String getDescription() { 38 | return this.description; 39 | } 40 | 41 | public List getFilePattern() { 42 | return this.filePattern; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/calendar/ScheduledWorkout.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.calendar; 2 | 3 | import java.time.LocalDate; 4 | import java.util.Objects; 5 | 6 | public class ScheduledWorkout { 7 | private final String name; 8 | private final String description; 9 | private final LocalDate date; 10 | 11 | public ScheduledWorkout(String name, String description, LocalDate date) { 12 | this.name = name; 13 | this.description = description; 14 | this.date = date; 15 | } 16 | 17 | public String getName() { 18 | return this.name; 19 | } 20 | 21 | public String getDescription() { 22 | return this.description; 23 | } 24 | 25 | public LocalDate getDate() { 26 | return this.date; 27 | } 28 | 29 | @Override 30 | public boolean equals(Object o) { 31 | if (this == o) return true; 32 | if (o == null || getClass() != o.getClass()) return false; 33 | ScheduledWorkout that = (ScheduledWorkout) o; 34 | return Objects.equals(name, that.name) && Objects.equals(description, that.description) && Objects.equals(date, that.date); 35 | } 36 | 37 | @Override 38 | public int hashCode() { 39 | return Objects.hash(name, description, date); 40 | } 41 | 42 | protected boolean canEqual(final Object other) { 43 | return other instanceof ScheduledWorkout; 44 | } 45 | 46 | public String toString() { 47 | return "ScheduledWorkout(name=" + this.getName() + ", description=" + this.getDescription() + ", date=" + this.getDate() + ")"; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/calendar/TrainingCalendarExport.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.calendar; 2 | 3 | import java.io.File; 4 | import java.time.LocalDate; 5 | import java.time.format.DateTimeFormatter; 6 | 7 | public class TrainingCalendarExport { 8 | private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TrainingCalendarExport.class); 9 | private static final DateTimeFormatter ddmmyyyy = DateTimeFormatter.ofPattern("dd/MM/yyyy"); 10 | 11 | public static void main(String[] args) { 12 | try { 13 | String inputFilename = args[0]; 14 | String outputFilename = args[1]; 15 | LocalDate raceDate = LocalDate.parse(args[2], ddmmyyyy); 16 | ProgrammeReader reader = new ProgrammeReader(inputFilename); 17 | Programme programme = reader.read(); 18 | Schedule schedule = ScheduleGenerator.generate(programme, raceDate); 19 | System.out.println(schedule); 20 | 21 | ScheduleWriter writer; 22 | if (outputFilename.endsWith(".xls")) { 23 | writer = new ScheduleExcelWriter(outputFilename); 24 | } else if (outputFilename.endsWith(".ics")) { 25 | writer = new ScheduleIcalWriter(new File(outputFilename)); 26 | } else { 27 | throw new RuntimeException("Unsupported file type " + outputFilename); 28 | } 29 | 30 | writer.write(schedule); 31 | } catch (Exception e) { 32 | log.error("ERROR", e); 33 | throw new RuntimeException(e); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/garmin/route/CoursePoint.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.garmin.route; 2 | 3 | import java.util.Objects; 4 | 5 | public class CoursePoint { 6 | private final double latitude; 7 | private final double longitude; 8 | private final double altitude; 9 | 10 | public CoursePoint(double latitude, double longitude, double altitude) { 11 | this.latitude = latitude; 12 | this.longitude = longitude; 13 | this.altitude = altitude; 14 | } 15 | 16 | public double getLatitude() { 17 | return this.latitude; 18 | } 19 | 20 | public double getLongitude() { 21 | return this.longitude; 22 | } 23 | 24 | public double getAltitude() { 25 | return this.altitude; 26 | } 27 | 28 | @Override 29 | public boolean equals(Object o) { 30 | if (this == o) return true; 31 | if (o == null || getClass() != o.getClass()) return false; 32 | CoursePoint that = (CoursePoint) o; 33 | return Double.compare(that.latitude, latitude) == 0 && Double.compare(that.longitude, longitude) == 0 && Double.compare(that.altitude, altitude) == 0; 34 | } 35 | 36 | @Override 37 | public int hashCode() { 38 | return Objects.hash(latitude, longitude, altitude); 39 | } 40 | 41 | protected boolean canEqual(final Object other) { 42 | return other instanceof CoursePoint; 43 | } 44 | 45 | public String toString() { 46 | return "CoursePoint(latitude=" + this.getLatitude() + ", longitude=" + this.getLongitude() + ", altitude=" + this.getAltitude() + ")"; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /common/src/main/java/com/johnpickup/garmin/common/unit/HeartRate.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.common.unit; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * Encapsulation of custom heart rate value with human-readable toString plus a conversion to Garmin units 7 | */ 8 | public class HeartRate { 9 | private final long value; 10 | private final HeartRateUnit unit; 11 | 12 | public HeartRate(long value, HeartRateUnit unit) { 13 | this.value = value; 14 | this.unit = unit; 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return String.format("%s%s", toValueString(), unit.getShortName()); 20 | } 21 | 22 | public Long toGarminHeartRate() { 23 | // Garmin HR units are in bpm + 100 ( 0 .. 100 reserved for HR %) 24 | return switch (unit) { 25 | case BEATS_PER_MINUTE -> value + 100; 26 | }; 27 | } 28 | 29 | public String toValueString() { 30 | return switch (unit) { 31 | case BEATS_PER_MINUTE -> String.format("%d", value); 32 | }; 33 | } 34 | 35 | @Override 36 | public boolean equals(Object o) { 37 | if (this == o) return true; 38 | if (o == null || getClass() != o.getClass()) return false; 39 | HeartRate heartRate = (HeartRate) o; 40 | return value == heartRate.value && unit == heartRate.unit; 41 | } 42 | 43 | @Override 44 | public int hashCode() { 45 | return Objects.hash(value, unit); 46 | } 47 | 48 | protected boolean canEqual(final Object other) { 49 | return other instanceof HeartRate; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/HeartRateZone.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.util.Objects; 4 | 5 | public class HeartRateZone implements HeartRate { 6 | private final Zone zone; 7 | private final long zoneNumber; 8 | private HeartRateZone(Zone zone, long zoneNumber) { 9 | this.zone = zone; 10 | this.zoneNumber = zoneNumber; 11 | } 12 | 13 | public static HeartRateZone Z1 = new HeartRateZone(Zone.Z1, 1); 14 | public static HeartRateZone Z2 = new HeartRateZone(Zone.Z2, 2); 15 | public static HeartRateZone Z3 = new HeartRateZone(Zone.Z3, 3); 16 | public static HeartRateZone Z4 = new HeartRateZone(Zone.Z4, 4); 17 | public static HeartRateZone Z5 = new HeartRateZone(Zone.Z5, 5); 18 | 19 | @Override 20 | public String toString() { 21 | return zone.name(); 22 | } 23 | 24 | @Override 25 | public boolean equals(Object o) { 26 | if (this == o) return true; 27 | if (o == null || getClass() != o.getClass()) return false; 28 | HeartRateZone that = (HeartRateZone) o; 29 | return zoneNumber == that.zoneNumber && zone == that.zone; 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(zone, zoneNumber); 35 | } 36 | 37 | protected boolean canEqual(final Object other) { 38 | return other instanceof HeartRateZone; 39 | } 40 | 41 | public long getZoneNumber() { 42 | return this.zoneNumber; 43 | } 44 | 45 | enum Zone { 46 | Z1, 47 | Z2, 48 | Z3, 49 | Z4, 50 | Z5 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/DistancePowerStep.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.util.Objects; 4 | 5 | public class DistancePowerStep extends Step { 6 | private final Distance distance; 7 | private final Power power; 8 | 9 | public DistancePowerStep(Distance distance, Power power) { 10 | super(); 11 | this.distance = distance; 12 | this.power = power; 13 | } 14 | 15 | public DistancePowerStep(StepIntensity stepIntensity, Distance distance, Power power) { 16 | super(stepIntensity); 17 | this.distance = distance; 18 | this.power = power; 19 | } 20 | @Override 21 | public String toString() { 22 | return distance + "@" + power + (stepIntensity==null?"":("|"+stepIntensity)); 23 | } 24 | 25 | @Override 26 | public boolean equals(Object o) { 27 | if (this == o) return true; 28 | if (o == null || getClass() != o.getClass()) return false; 29 | DistancePowerStep that = (DistancePowerStep) o; 30 | return Objects.equals(distance, that.distance) && Objects.equals(power, that.power) 31 | && Objects.equals(stepIntensity, that.stepIntensity); 32 | } 33 | 34 | @Override 35 | public int hashCode() { 36 | return Objects.hash(distance, power); 37 | } 38 | 39 | protected boolean canEqual(final Object other) { 40 | return other instanceof DistancePowerStep; 41 | } 42 | 43 | public Distance getDistance() { 44 | return this.distance; 45 | } 46 | 47 | public Power getPower() { 48 | return this.power; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /common/src/main/java/com/johnpickup/garmin/common/unit/CustomPowerTarget.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.common.unit; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * Power target - a minimum and maximum power in watts 7 | */ 8 | public class CustomPowerTarget extends PowerTarget { 9 | private final Power maxPower; 10 | private final Power minPower; 11 | 12 | public CustomPowerTarget(long min, long max, PowerUnit unit) { 13 | this.minPower = new Power(min, unit); 14 | this.maxPower = new Power(max, unit); 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return minPower.toValueString() + "-" + maxPower; 20 | } 21 | 22 | public Long getGarminLow() { 23 | if (minPower.toGarminPower() < maxPower.toGarminPower()) 24 | return minPower.toGarminPower(); 25 | else 26 | return maxPower.toGarminPower(); 27 | } 28 | 29 | public Long getGarminHigh() { 30 | if (minPower.toGarminPower() < maxPower.toGarminPower()) 31 | return maxPower.toGarminPower(); 32 | else 33 | return minPower.toGarminPower(); 34 | } 35 | 36 | @Override 37 | public Long getTargetValue() { 38 | return 0L; 39 | } 40 | 41 | @Override 42 | public boolean equals(Object o) { 43 | if (o == null || getClass() != o.getClass()) return false; 44 | CustomPowerTarget that = (CustomPowerTarget) o; 45 | return Objects.equals(maxPower, that.maxPower) && Objects.equals(minPower, that.minPower); 46 | } 47 | 48 | @Override 49 | public int hashCode() { 50 | return Objects.hash(maxPower, minPower); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/DistancePaceStep.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * Created by john on 03/01/2017. 7 | */ 8 | public class DistancePaceStep extends Step { 9 | private final Distance distance; 10 | private final Pace pace; 11 | 12 | public DistancePaceStep(Distance distance, Pace pace) { 13 | super(); 14 | this.distance = distance; 15 | this.pace = pace; 16 | } 17 | 18 | public DistancePaceStep(StepIntensity stepIntensity, Distance distance, Pace pace) { 19 | super(stepIntensity); 20 | this.distance = distance; 21 | this.pace = pace; 22 | } 23 | @Override 24 | public String toString() { 25 | return distance + "@" + pace + (stepIntensity==null?"":("|"+stepIntensity)); 26 | } 27 | 28 | @Override 29 | public boolean equals(Object o) { 30 | if (this == o) return true; 31 | if (o == null || getClass() != o.getClass()) return false; 32 | DistancePaceStep that = (DistancePaceStep) o; 33 | return Objects.equals(distance, that.distance) && Objects.equals(pace, that.pace) 34 | && Objects.equals(stepIntensity, that.stepIntensity); 35 | } 36 | 37 | @Override 38 | public int hashCode() { 39 | return Objects.hash(distance, pace); 40 | } 41 | 42 | protected boolean canEqual(final Object other) { 43 | return other instanceof DistancePaceStep; 44 | } 45 | 46 | public Distance getDistance() { 47 | return this.distance; 48 | } 49 | 50 | public Pace getPace() { 51 | return this.pace; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/TimeHeartRateStep.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * Created by john on 03/01/2017. 7 | */ 8 | public class TimeHeartRateStep extends Step { 9 | private final Time time; 10 | private final HeartRate heartRate; 11 | 12 | public TimeHeartRateStep(Time time, HeartRate heartRate) { 13 | super(); 14 | this.time = time; 15 | this.heartRate = heartRate; 16 | } 17 | 18 | public TimeHeartRateStep(StepIntensity stepIntensity, Time time, HeartRate heartRate) { 19 | super(stepIntensity); 20 | this.time = time; 21 | this.heartRate = heartRate; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return time + "@" + heartRate + (stepIntensity==null?"":("|"+stepIntensity)); 27 | } 28 | 29 | @Override 30 | public boolean equals(Object o) { 31 | if (this == o) return true; 32 | if (o == null || getClass() != o.getClass()) return false; 33 | TimeHeartRateStep that = (TimeHeartRateStep) o; 34 | return Objects.equals(time, that.time) && Objects.equals(heartRate, that.heartRate) 35 | && Objects.equals(stepIntensity, that.stepIntensity); 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | return Objects.hash(time, heartRate); 41 | } 42 | 43 | protected boolean canEqual(final Object other) { 44 | return other instanceof TimeHeartRateStep; 45 | } 46 | 47 | public Time getTime() { 48 | return this.time; 49 | } 50 | 51 | public HeartRate getHeartRate() { 52 | return this.heartRate; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/garmin/fit/FitReader.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.garmin.fit; 2 | 3 | import com.garmin.fit.Decode; 4 | import com.garmin.fit.Field; 5 | import com.garmin.fit.Mesg; 6 | import com.garmin.fit.MesgBroadcaster; 7 | 8 | import java.io.File; 9 | import java.io.FileInputStream; 10 | import java.io.IOException; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public class FitReader { 15 | public static List read(File input) throws IOException { 16 | List result = new ArrayList<>(); 17 | 18 | FileInputStream in = null; 19 | try { 20 | in = new FileInputStream(input); 21 | Decode decode = new Decode(); 22 | MesgBroadcaster mesgBroadcaster = new MesgBroadcaster(decode); 23 | 24 | decode.read(in, result::add); 25 | } 26 | finally { 27 | if (in != null) { 28 | in.close(); 29 | } 30 | } 31 | return result; 32 | } 33 | 34 | public static void main(String[] args) { 35 | for (String arg : args) { 36 | System.out.println(arg); 37 | try { 38 | List mesgs = read(new File(arg)); 39 | for (Mesg mesg : mesgs) { 40 | System.out.println("MESSAGE"); 41 | for (Field field : mesg.getFields()) { 42 | System.out.println(field.getName()); 43 | System.out.println(field.getLongValue()); 44 | } 45 | 46 | } 47 | 48 | } catch (IOException e) { 49 | throw new RuntimeException(e); 50 | } 51 | } 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/PowerZone.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.util.Objects; 4 | 5 | public class PowerZone implements Power { 6 | private final Zone zone; 7 | private final long zoneNumber; 8 | private PowerZone(Zone zone, long zoneNumber) { 9 | this.zone = zone; 10 | this.zoneNumber = zoneNumber; 11 | } 12 | 13 | public static PowerZone PZ1 = new PowerZone(Zone.PZ1, 1); 14 | public static PowerZone PZ2 = new PowerZone(Zone.PZ2, 2); 15 | public static PowerZone PZ3 = new PowerZone(Zone.PZ3, 3); 16 | public static PowerZone PZ4 = new PowerZone(Zone.PZ4, 4); 17 | public static PowerZone PZ5 = new PowerZone(Zone.PZ5, 5); 18 | public static PowerZone PZ6 = new PowerZone(Zone.PZ6, 6); 19 | public static PowerZone PZ7 = new PowerZone(Zone.PZ7, 7); 20 | 21 | @Override 22 | public String toString() { 23 | return zone.name(); 24 | } 25 | 26 | @Override 27 | public boolean equals(Object o) { 28 | if (this == o) return true; 29 | if (o == null || getClass() != o.getClass()) return false; 30 | PowerZone that = (PowerZone) o; 31 | return zoneNumber == that.zoneNumber && zone == that.zone; 32 | } 33 | 34 | @Override 35 | public int hashCode() { 36 | return Objects.hash(zone, zoneNumber); 37 | } 38 | 39 | protected boolean canEqual(final Object other) { 40 | return other instanceof PowerZone; 41 | } 42 | 43 | public long getZoneNumber() { 44 | return this.zoneNumber; 45 | } 46 | 47 | enum Zone { 48 | PZ1, 49 | PZ2, 50 | PZ3, 51 | PZ4, 52 | PZ5, 53 | PZ6, 54 | PZ7 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/Workout.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.util.List; 4 | import java.util.Objects; 5 | import java.util.Optional; 6 | 7 | /** 8 | * Created by john on 03/01/2017. 9 | */ 10 | public class Workout { 11 | private final List steps; 12 | private Sport sport; 13 | private Integer poolLength; 14 | 15 | public Workout(List steps) { 16 | this.steps = steps; 17 | } 18 | 19 | @Override 20 | public String toString() { 21 | String result = null; 22 | for (Step step : steps) { 23 | result = result==null?step.toString():result + " + " + step.toString(); 24 | } 25 | return result; 26 | } 27 | 28 | public List getSteps() { 29 | return this.steps; 30 | } 31 | 32 | public Sport getSport() { 33 | return Optional.ofNullable(sport).orElse(Sport.RUNNING); 34 | } 35 | 36 | public void setSport(Sport sport) { 37 | this.sport = sport; 38 | } 39 | 40 | public void setPoolLength(Integer poolLength) { 41 | this.poolLength = poolLength; 42 | } 43 | 44 | public Integer getPoolLength() { 45 | return poolLength; 46 | } 47 | 48 | @Override 49 | public boolean equals(Object o) { 50 | if (this == o) return true; 51 | if (o == null || getClass() != o.getClass()) return false; 52 | Workout workout = (Workout) o; 53 | return Objects.equals(steps, workout.steps); 54 | } 55 | 56 | @Override 57 | public int hashCode() { 58 | return Objects.hash(steps); 59 | } 60 | 61 | protected boolean canEqual(final Object other) { 62 | return other instanceof Workout; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/DistanceHeartRateStep.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * Created by john on 03/01/2017. 7 | */ 8 | public class DistanceHeartRateStep extends Step { 9 | private final Distance distance; 10 | private final HeartRate heartRate; 11 | 12 | public DistanceHeartRateStep(Distance distance, HeartRate heartRate) { 13 | super(); 14 | this.distance = distance; 15 | this.heartRate = heartRate; 16 | } 17 | 18 | public DistanceHeartRateStep(StepIntensity stepIntensity, Distance distance, HeartRate heartRate) { 19 | super(stepIntensity); 20 | this.distance = distance; 21 | this.heartRate = heartRate; 22 | } 23 | @Override 24 | public String toString() { 25 | return distance + "@" + heartRate + (stepIntensity==null?"":("|"+stepIntensity)); 26 | } 27 | 28 | @Override 29 | public boolean equals(Object o) { 30 | if (this == o) return true; 31 | if (o == null || getClass() != o.getClass()) return false; 32 | DistanceHeartRateStep that = (DistanceHeartRateStep) o; 33 | return Objects.equals(distance, that.distance) && Objects.equals(heartRate, that.heartRate) 34 | && Objects.equals(stepIntensity, that.stepIntensity); 35 | } 36 | 37 | @Override 38 | public int hashCode() { 39 | return Objects.hash(distance, heartRate); 40 | } 41 | 42 | protected boolean canEqual(final Object other) { 43 | return other instanceof DistanceHeartRateStep; 44 | } 45 | 46 | public Distance getDistance() { 47 | return this.distance; 48 | } 49 | 50 | public HeartRate getHeartRate() { 51 | return this.heartRate; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/Time.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * Created by john on 03/01/2017. 7 | */ 8 | public class Time { 9 | private final Integer minutes; 10 | private final Integer seconds; 11 | 12 | public Time(Integer minutes, Integer seconds) { 13 | this.minutes = minutes; 14 | this.seconds = seconds; 15 | } 16 | 17 | public static Time parseTime(String text) { 18 | String[] parts = text.split(":"); 19 | if (parts.length == 2) { 20 | return new Time(Integer.parseInt(parts[0]), Integer.parseInt(parts[1])); 21 | } 22 | else { 23 | throw new RuntimeException("Invalid time " + text); 24 | } 25 | } 26 | 27 | /** 28 | * Return the time as a number of minutes 29 | * @return this time in fraction of a minute units 30 | */ 31 | public Double asDouble() { 32 | if (minutes != null && seconds != null) { 33 | return (minutes + seconds/60.0); 34 | } 35 | return null; 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | return String.format("%d:%02d", minutes, seconds); 41 | } 42 | 43 | @Override 44 | public boolean equals(Object o) { 45 | if (this == o) return true; 46 | if (o == null || getClass() != o.getClass()) return false; 47 | Time time = (Time) o; 48 | return Objects.equals(minutes, time.minutes) && Objects.equals(seconds, time.seconds); 49 | } 50 | 51 | @Override 52 | public int hashCode() { 53 | return Objects.hash(minutes, seconds); 54 | } 55 | 56 | protected boolean canEqual(final Object other) { 57 | return other instanceof Time; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /parser/src/main/java/com/johnpickup/garmin/parser/ScheduledWorkout.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.parser; 2 | 3 | import java.time.LocalDate; 4 | import java.util.Objects; 5 | 6 | /** 7 | * Created by john on 11/01/2017. 8 | */ 9 | public class ScheduledWorkout { 10 | private final LocalDate date; 11 | private final Workout workout; 12 | private final String name; 13 | private final String description; 14 | 15 | public ScheduledWorkout(LocalDate date, Workout workout, String name, String description) { 16 | this.date = date; 17 | this.workout = workout; 18 | this.name = name; 19 | this.description = description; 20 | } 21 | 22 | public LocalDate getDate() { 23 | return this.date; 24 | } 25 | 26 | public Workout getWorkout() { 27 | return this.workout; 28 | } 29 | 30 | public String getName() { 31 | return this.name; 32 | } 33 | 34 | public String getDescription() { 35 | return this.description; 36 | } 37 | 38 | @Override 39 | public boolean equals(Object o) { 40 | if (this == o) return true; 41 | if (o == null || getClass() != o.getClass()) return false; 42 | ScheduledWorkout that = (ScheduledWorkout) o; 43 | return Objects.equals(date, that.date) && Objects.equals(workout, that.workout) && Objects.equals(name, that.name) && Objects.equals(description, that.description); 44 | } 45 | 46 | @Override 47 | public int hashCode() { 48 | return Objects.hash(date, workout, name, description); 49 | } 50 | 51 | protected boolean canEqual(final Object other) { 52 | return other instanceof ScheduledWorkout; 53 | } 54 | 55 | public String toString() { 56 | return "ScheduledWorkout(date=" + this.getDate() + ", workout=" + this.getWorkout() + ", name=" + this.getName() + ", description=" + this.getDescription() + ")"; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/garmin/workout/TimeWorkoutStep.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.garmin.workout; 2 | 3 | import com.garmin.fit.Intensity; 4 | import com.garmin.fit.WktStepDuration; 5 | import com.garmin.fit.WktStepTarget; 6 | import com.garmin.fit.WorkoutStepMesg; 7 | import com.johnpickup.garmin.common.unit.Time; 8 | 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.Objects; 12 | 13 | /** 14 | * Simple workout that lasts a specific distance, no pace targets 15 | */ 16 | public class TimeWorkoutStep extends WorkoutStep { 17 | private final Time time; 18 | 19 | public TimeWorkoutStep(Intensity intensity, Time time) { 20 | super(intensity); 21 | this.time = time; 22 | } 23 | 24 | @Override 25 | public String getName() { 26 | return time.toString(); 27 | } 28 | 29 | @Override 30 | public List generateWorkoutSteps() { 31 | WorkoutStepMesg step = new WorkoutStepMesg(); 32 | step.setIntensity(intensity); 33 | step.setDurationType(WktStepDuration.TIME); 34 | step.setDurationDistance(time.toGarminTime()); 35 | step.setTargetType(WktStepTarget.OPEN); 36 | step.setMessageIndex(generateWorkoutStepIndex()); 37 | step.setTargetValue(0L); 38 | step.setNotes(nameWithIntensity()); 39 | 40 | return Collections.singletonList(step); 41 | } 42 | 43 | @Override 44 | public boolean equals(Object o) { 45 | if (this == o) return true; 46 | if (o == null || getClass() != o.getClass()) return false; 47 | TimeWorkoutStep that = (TimeWorkoutStep) o; 48 | return Objects.equals(time, that.time); 49 | } 50 | 51 | @Override 52 | public int hashCode() { 53 | return Objects.hash(time); 54 | } 55 | 56 | protected boolean canEqual(final Object other) { 57 | return other instanceof TimeWorkoutStep; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/excel/ExcelUtils.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.excel; 2 | 3 | import com.johnpickup.garmin.parser.Sport; 4 | import org.apache.poi.ss.usermodel.CellType; 5 | import org.apache.poi.ss.usermodel.Row; 6 | 7 | import java.util.Optional; 8 | 9 | public class ExcelUtils { 10 | public static String readStringValue(Row row, Integer index) { 11 | if (index == null || row.getCell(index) == null || row.getCell(index).getCellType() != CellType.STRING) return null; 12 | return row.getCell(index).getStringCellValue(); 13 | } 14 | 15 | public static Integer readIntValue(Row row, Integer index) { 16 | if (index == null || row.getCell(index) == null || row.getCell(index).getCellType() != CellType.NUMERIC) return null; 17 | return (int) row.getCell(index).getNumericCellValue(); 18 | } 19 | 20 | public static Sport readSportValue(Row row, Integer index) { 21 | String sportText = ExcelUtils.readStringValue(row, index); 22 | return switch (Optional.ofNullable(sportText).map(String::toUpperCase).orElse("")) { 23 | case "RUNNING", "RUN" -> Sport.RUNNING; 24 | case "ROAD RUNNING" -> Sport.ROAD_RUNNING; 25 | case "TRAIL RUNNING" -> Sport.TRAIL_RUNNING; 26 | case "CYCLING", "CYCLE" -> Sport.CYCLING; 27 | case "ROAD CYCLING" -> Sport.ROAD_CYCLING; 28 | case "MTB", "MOUNTAIN BIKING" -> Sport.MTB; 29 | case "SWIMMING", "SWIM" -> Sport.SWIMMING; 30 | case "POOL SWIMMING", "POOL" -> Sport.POOL_SWIMMING; 31 | case "OPEN WATER SWIMMING", "OPEN WATER" -> Sport.OPEN_WATER_SWIMMING; 32 | case "CARDIO" -> Sport.CARDIO; 33 | case "STRENGTH" -> Sport.STRENGTH; 34 | case "HIIT" -> Sport.HIIT; 35 | case "PILATES" -> Sport.PILATES; 36 | case "YOGA" -> Sport.YOGA; 37 | default -> Sport.RUNNING; 38 | }; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/converter/StepConverterFactory.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.converter; 2 | 3 | import com.johnpickup.garmin.parser.*; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | * Factory that given a type of workout step with return an instance of the appropriate converter class 10 | */ 11 | public class StepConverterFactory { 12 | private static StepConverterFactory instance; 13 | private final Map converters = new HashMap<>(); 14 | 15 | private StepConverterFactory() { 16 | register(new DistanceStepConverter(), DistanceStep.class); 17 | register(new DistancePaceStepConverter(), DistancePaceStep.class); 18 | register(new DistanceHeartRateStepConverter(), DistanceHeartRateStep.class); 19 | register(new DistancePowerStepConverter(), DistancePowerStep.class); 20 | register(new TimeStepConverter(), TimeStep.class); 21 | register(new TimePaceStepConverter(), TimePaceStep.class); 22 | register(new TimeHeartRateStepConverter(), TimeHeartRateStep.class); 23 | register(new TimePowerStepConverter(), TimePowerStep.class); 24 | register(new OpenStepConverter(), OpenStep.class); 25 | register(new OpenPaceStepConverter(), OpenPaceStep.class); 26 | register(new OpenHeartRateStepConverter(), OpenHeartRateStep.class); 27 | register(new OpenPowerStepConverter(), OpenPowerStep.class); 28 | register(new RepeatingStepsConverter(), RepeatingSteps.class); 29 | } 30 | 31 | public static StepConverterFactory getInstance() { 32 | if (instance == null) { 33 | instance = new StepConverterFactory(); 34 | } 35 | return instance; 36 | } 37 | 38 | private void register(StepConverter converter,Class stepClass) { 39 | converters.put(stepClass, converter); 40 | } 41 | 42 | public StepConverter createFor(Step step) { 43 | return converters.get(step.getClass()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /common/src/main/java/com/johnpickup/garmin/common/unit/CustomHeartRateTarget.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.garmin.common.unit; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * Heart Rate target - a minimum and maximum HR 7 | */ 8 | public class CustomHeartRateTarget extends HeartRateTarget { 9 | private final HeartRate maxHeartRate; 10 | private final HeartRate minHeartRate; 11 | 12 | public CustomHeartRateTarget(long min, long max, HeartRateUnit unit) { 13 | this.minHeartRate = new HeartRate(min, unit); 14 | this.maxHeartRate = new HeartRate(max, unit); 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return minHeartRate.toValueString() + "-" + maxHeartRate; 20 | } 21 | 22 | public Long getGarminLow() { 23 | if (minHeartRate.toGarminHeartRate() < maxHeartRate.toGarminHeartRate()) 24 | return minHeartRate.toGarminHeartRate(); 25 | else 26 | return maxHeartRate.toGarminHeartRate(); 27 | } 28 | 29 | public Long getGarminHigh() { 30 | if (minHeartRate.toGarminHeartRate() < maxHeartRate.toGarminHeartRate()) 31 | return maxHeartRate.toGarminHeartRate(); 32 | else 33 | return minHeartRate.toGarminHeartRate(); 34 | } 35 | 36 | @Override 37 | public Long getTargetValue() { 38 | return 0L; 39 | } 40 | 41 | protected boolean canEqual(final Object other) { 42 | return other instanceof CustomHeartRateTarget; 43 | } 44 | 45 | @Override 46 | public boolean equals(Object o) { 47 | if (this == o) return true; 48 | if (o == null || getClass() != o.getClass()) return false; 49 | CustomHeartRateTarget that = (CustomHeartRateTarget) o; 50 | return Objects.equals(maxHeartRate, that.maxHeartRate) && Objects.equals(minHeartRate, that.minHeartRate); 51 | } 52 | 53 | @Override 54 | public int hashCode() { 55 | return Objects.hash(maxHeartRate, minHeartRate); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/garmin/workout/DistanceWorkoutStep.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.garmin.workout; 2 | 3 | import com.garmin.fit.Intensity; 4 | import com.garmin.fit.WktStepDuration; 5 | import com.garmin.fit.WktStepTarget; 6 | import com.garmin.fit.WorkoutStepMesg; 7 | import com.johnpickup.garmin.common.unit.Distance; 8 | 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.Objects; 12 | 13 | /** 14 | * Simple workout that lasts a specific distance, no pace targets 15 | */ 16 | public class DistanceWorkoutStep extends WorkoutStep { 17 | private final Distance distance; 18 | 19 | public DistanceWorkoutStep(Intensity intensity, Distance distance) { 20 | super(intensity); 21 | this.distance = distance; 22 | } 23 | 24 | @Override 25 | public String getName() { 26 | return distance.toString(); 27 | } 28 | 29 | @Override 30 | public List generateWorkoutSteps() { 31 | WorkoutStepMesg step = new WorkoutStepMesg(); 32 | step.setIntensity(intensity); 33 | step.setDurationType(WktStepDuration.DISTANCE); 34 | step.setDurationDistance(distance.toGarminDistance()); 35 | step.setTargetType(WktStepTarget.OPEN); 36 | step.setMessageIndex(generateWorkoutStepIndex()); 37 | step.setTargetValue(0L); 38 | step.setNotes(nameWithIntensity()); 39 | 40 | return Collections.singletonList(step); 41 | } 42 | 43 | @Override 44 | public boolean equals(Object o) { 45 | if (this == o) return true; 46 | if (o == null || getClass() != o.getClass()) return false; 47 | DistanceWorkoutStep that = (DistanceWorkoutStep) o; 48 | return Objects.equals(distance, that.distance); 49 | } 50 | 51 | @Override 52 | public int hashCode() { 53 | return Objects.hash(distance); 54 | } 55 | 56 | protected boolean canEqual(final Object other) { 57 | return other instanceof DistanceWorkoutStep; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/calendar/ProgrammeWriter.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.calendar; 2 | 3 | import org.apache.poi.hssf.usermodel.HSSFWorkbook; 4 | import org.apache.poi.ss.usermodel.Row; 5 | import org.apache.poi.ss.usermodel.Sheet; 6 | import org.apache.poi.ss.usermodel.Workbook; 7 | 8 | import java.io.FileOutputStream; 9 | import java.io.IOException; 10 | import java.util.ArrayList; 11 | import java.util.Collections; 12 | import java.util.Comparator; 13 | import java.util.List; 14 | 15 | public class ProgrammeWriter { 16 | 17 | private String filename; 18 | private int rowIdx; 19 | 20 | public ProgrammeWriter(String filename) { 21 | this.filename = filename; 22 | } 23 | 24 | public void write(Programme programme) throws IOException { 25 | Workbook wb = new HSSFWorkbook(); 26 | FileOutputStream fileOut = new FileOutputStream(filename); 27 | 28 | Sheet sheet = wb.createSheet("Programme"); 29 | 30 | writeHeader(sheet); 31 | 32 | rowIdx = 1; 33 | 34 | List workouts = new ArrayList<>(programme.getWorkouts()); 35 | Collections.sort(workouts, Comparator.comparingLong(arg0 -> arg0.getOffset())); 36 | 37 | for (PlannedWorkout workout : workouts) { 38 | writeWorkout(sheet, workout); 39 | rowIdx++; 40 | } 41 | 42 | wb.write(fileOut); 43 | fileOut.close(); 44 | } 45 | 46 | private void writeWorkout(Sheet sheet, PlannedWorkout workout) { 47 | Row row = sheet.createRow(rowIdx); 48 | row.createCell(0).setCellValue(workout.getName()); 49 | row.createCell(1).setCellValue(workout.getDescription()); 50 | row.createCell(2).setCellValue(workout.getOffset()); 51 | } 52 | 53 | private void writeHeader(Sheet sheet) { 54 | Row row = sheet.createRow(rowIdx); 55 | row.createCell(0).setCellValue("Name"); 56 | row.createCell(1).setCellValue("Description"); 57 | row.createCell(2).setCellValue("Offset"); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/parser/WorkoutTextParser.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.parser; 2 | 3 | import com.johnpickup.garmin.parser.AntlrErrorHandler; 4 | import com.johnpickup.garmin.parser.Workout; 5 | import com.johnpickup.garmin.parser.WorkoutLexer; 6 | import com.johnpickup.garmin.parser.WorkoutParser; 7 | import org.antlr.v4.runtime.CharStream; 8 | import org.antlr.v4.runtime.CharStreams; 9 | import org.antlr.v4.runtime.CommonTokenStream; 10 | import org.antlr.v4.runtime.TokenStream; 11 | 12 | import java.io.ByteArrayInputStream; 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | 16 | 17 | /** 18 | * Created by john on 03/01/2017. 19 | */ 20 | public class WorkoutTextParser { 21 | private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(WorkoutTextParser.class); 22 | public Workout parse(String workoutInput) { 23 | try { 24 | InputStream input = new ByteArrayInputStream(workoutInput.getBytes()); 25 | CharStream charStream = CharStreams.fromStream(input); 26 | WorkoutLexer lexer = new WorkoutLexer(charStream); 27 | TokenStream tokens = new CommonTokenStream(lexer); 28 | WorkoutParser parser = new WorkoutParser(tokens); 29 | AntlrErrorHandler errorHandler = new AntlrErrorHandler(); 30 | parser.setErrorHandler(errorHandler); 31 | Workout result = parser.workout().w; 32 | if (errorHandler.isHadError()) { 33 | log.error("Parser error reading {} : {}", workoutInput, errorHandler.getErrorMessage()); 34 | throw new RuntimeException("Error parsing " + workoutInput + 35 | (errorHandler.getErrorMessage() == null ? 36 | "" : 37 | (" - " + errorHandler.getErrorMessage())) 38 | ); 39 | } else { 40 | return result; 41 | } 42 | } 43 | catch (IOException ex) { 44 | throw new RuntimeException(ex); 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/garmin/workout/OpenPaceWorkoutStep.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.garmin.workout; 2 | 3 | import com.garmin.fit.Intensity; 4 | import com.garmin.fit.WktStepDuration; 5 | import com.garmin.fit.WktStepTarget; 6 | import com.garmin.fit.WorkoutStepMesg; 7 | import com.johnpickup.garmin.common.unit.PaceTarget; 8 | 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.Objects; 12 | 13 | /** 14 | * Simple workout the lasts a specific distance with a pace target 15 | */ 16 | public class OpenPaceWorkoutStep extends WorkoutStep { 17 | private final PaceTarget paceTarget; 18 | 19 | public OpenPaceWorkoutStep(Intensity intensity, PaceTarget paceTarget) { 20 | super(intensity); 21 | this.paceTarget = paceTarget; 22 | } 23 | 24 | @Override 25 | public String getName() { 26 | return "Open " + paceTarget.toString(); 27 | } 28 | 29 | @Override 30 | public List generateWorkoutSteps() { 31 | WorkoutStepMesg step = new WorkoutStepMesg(); 32 | step.setIntensity(intensity); 33 | step.setDurationType(WktStepDuration.OPEN); 34 | step.setTargetType(WktStepTarget.SPEED); 35 | step.setTargetValue(0L); 36 | step.setMessageIndex(generateWorkoutStepIndex()); 37 | step.setCustomTargetValueLow(paceTarget.getGarminLow()); 38 | step.setCustomTargetValueHigh(paceTarget.getGarminHigh()); 39 | step.setNotes(nameWithIntensity()); 40 | 41 | return Collections.singletonList(step); 42 | } 43 | 44 | @Override 45 | public boolean equals(Object o) { 46 | if (this == o) return true; 47 | if (o == null || getClass() != o.getClass()) return false; 48 | OpenPaceWorkoutStep that = (OpenPaceWorkoutStep) o; 49 | return Objects.equals(paceTarget, that.paceTarget); 50 | } 51 | 52 | @Override 53 | public int hashCode() { 54 | return Objects.hash(paceTarget); 55 | } 56 | 57 | protected boolean canEqual(final Object other) { 58 | return other instanceof OpenPaceWorkoutStep; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/johnpickup/app/excel/PaceSheetReader.java: -------------------------------------------------------------------------------- 1 | package com.johnpickup.app.excel; 2 | 3 | import com.johnpickup.app.parser.PaceRangeTextParser; 4 | import com.johnpickup.garmin.parser.Pace; 5 | import org.apache.poi.ss.usermodel.Cell; 6 | import org.apache.poi.ss.usermodel.CellType; 7 | import org.apache.poi.ss.usermodel.Row; 8 | import org.apache.poi.ss.usermodel.Sheet; 9 | 10 | import java.util.Collections; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | import java.util.Optional; 14 | 15 | /** 16 | * Created by john on 10/01/2017. 17 | */ 18 | public class PaceSheetReader { 19 | private int nameIndex=0; 20 | private int valueIndex=1; 21 | private final PaceRangeTextParser parser = new PaceRangeTextParser(); 22 | 23 | public Map readPaces(Sheet sheet) { 24 | if (sheet == null) return Collections.emptyMap(); 25 | Map result = new HashMap<>(); 26 | 27 | int rowIdx=0; 28 | for (Row row : sheet) { 29 | if (rowIdx++==0) { 30 | readHeaderRow(row); 31 | } 32 | else { 33 | String name = readName(row); 34 | Pace pace = readPace(row); 35 | if (name != null && pace != null) { 36 | result.put(name, pace); 37 | } 38 | } 39 | } 40 | 41 | return result; 42 | } 43 | 44 | private String readName(Row row) { 45 | return ExcelUtils.readStringValue(row, nameIndex); 46 | } 47 | 48 | private Pace readPace(Row row) { 49 | return Optional.ofNullable(ExcelUtils.readStringValue(row, valueIndex)) 50 | .map(parser::parse) 51 | .orElse(null); 52 | } 53 | 54 | private void readHeaderRow(Row row) { 55 | for (Cell cell : row) { 56 | if (cell.getCellType() != CellType.STRING) continue; 57 | 58 | if ("Name".equals(cell.getStringCellValue())) nameIndex = cell.getColumnIndex(); 59 | if ("Value".equals(cell.getStringCellValue())) valueIndex = cell.getColumnIndex(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/resources/javafx/main-form.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |