├── .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 |
5 |
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 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
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 extends Step> steps;
12 | private Sport sport;
13 | private Integer poolLength;
14 |
15 | public Workout(List extends Step> 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 extends Step> 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 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/parser/src/main/java/com/johnpickup/garmin/parser/RepeatingSteps.java:
--------------------------------------------------------------------------------
1 | package com.johnpickup.garmin.parser;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.Objects;
6 |
7 | /**
8 | * Created by john on 03/01/2017.
9 | */
10 | public class RepeatingSteps extends Step {
11 | private final List steps = new ArrayList<>();
12 | private int repetitions = 1;
13 |
14 | public RepeatingSteps(Step firstStep) {
15 | steps.add(firstStep);
16 | }
17 |
18 | public RepeatingSteps() {
19 | }
20 |
21 | public RepeatingSteps(List steps) {
22 | this.steps.addAll(steps);
23 | }
24 |
25 | public void addStep(Step step) {
26 | steps.add(step);
27 | }
28 |
29 | @Override
30 | public String toString() {
31 | if (steps.isEmpty()) return "";
32 | StringBuilder result = null;
33 | for (Step step : steps) {
34 | if (result == null) {
35 | result = new StringBuilder("(" + step.toString());
36 | }
37 | else {
38 | result.append(" + ").append(step.toString());
39 | }
40 | }
41 | result.append(") * ").append(repetitions);
42 |
43 | return result.toString();
44 | }
45 |
46 | @Override
47 | public boolean equals(Object o) {
48 | if (this == o) return true;
49 | if (o == null || getClass() != o.getClass()) return false;
50 | RepeatingSteps that = (RepeatingSteps) o;
51 | return repetitions == that.repetitions && Objects.equals(steps, that.steps);
52 | }
53 |
54 | @Override
55 | public int hashCode() {
56 | return Objects.hash(steps, repetitions);
57 | }
58 |
59 | protected boolean canEqual(final Object other) {
60 | return other instanceof RepeatingSteps;
61 | }
62 |
63 | public List getSteps() {
64 | return this.steps;
65 | }
66 |
67 | public int getRepetitions() {
68 | return this.repetitions;
69 | }
70 |
71 | public void setRepetitions(int repetitions) {
72 | this.repetitions = repetitions;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/app/src/main/java/com/johnpickup/app/garmin/workout/OpenPowerWorkoutStep.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.PowerTarget;
8 |
9 | import java.util.Collections;
10 | import java.util.List;
11 | import java.util.Objects;
12 |
13 | /**
14 | * Simple workout step that's open with a heart rate target
15 | */
16 | public class OpenPowerWorkoutStep extends WorkoutStep {
17 | private final PowerTarget powerTarget;
18 |
19 | public OpenPowerWorkoutStep(Intensity intensity, PowerTarget powerTarget) {
20 | super(intensity);
21 | this.powerTarget = powerTarget;
22 | }
23 |
24 | @Override
25 | public String getName() {
26 | return "Open " + powerTarget.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.POWER);
35 | step.setTargetValue(powerTarget.getTargetValue());
36 | step.setMessageIndex(generateWorkoutStepIndex());
37 | step.setCustomTargetValueLow(powerTarget.getGarminLow());
38 | step.setCustomTargetValueHigh(powerTarget.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 | OpenPowerWorkoutStep that = (OpenPowerWorkoutStep) o;
49 | return Objects.equals(powerTarget, that.powerTarget);
50 | }
51 |
52 | @Override
53 | public int hashCode() {
54 | return Objects.hash(powerTarget);
55 | }
56 |
57 | protected boolean canEqual(final Object other) {
58 | return other instanceof OpenPowerWorkoutStep;
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/java/com/johnpickup/app/garmin/converter/CourseConverter.java:
--------------------------------------------------------------------------------
1 | package com.johnpickup.app.garmin.converter;
2 |
3 | import com.johnpickup.app.garmin.route.Course;
4 | import com.johnpickup.app.garmin.route.CoursePoint;
5 | import com.johnpickup.gpx.*;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | public class CourseConverter {
11 | public Course convert(GpxType gpx) {
12 | String alternativeName = gpx.getMetadata().getName();
13 |
14 | if (gpx.getTrk().size() > 0) {
15 | return convertTrack(gpx.getTrk().get(0), alternativeName);
16 | }
17 |
18 | if (gpx.getRte().size() > 0) {
19 | return convertRoute(gpx.getRte().get(0), alternativeName);
20 | }
21 |
22 | throw new RuntimeException("GPX doesn't contain a route or a track, nothing to convert");
23 | }
24 |
25 | private Course convertTrack(TrkType trkType, String alternativeName) {
26 | String name = trkType.getName()==null || trkType.getName().isEmpty()? alternativeName : trkType.getName();
27 | List points = new ArrayList<>();
28 | for (TrksegType trkSeg : trkType.getTrkseg()) {
29 | for (WptType wptType : trkSeg.getTrkpt()) {
30 | points.add(wayPointToCoursePoint(wptType));
31 | }
32 | }
33 | return new Course(name, points);
34 | }
35 |
36 | private Course convertRoute(RteType rteType, String alternativeName) {
37 | String name = rteType.getName() == null || rteType.getName().isEmpty() ? alternativeName : rteType.getName();
38 | List points = new ArrayList<>();
39 | for (WptType wptType : rteType.getRtept()) {
40 | points.add(wayPointToCoursePoint(wptType));
41 | }
42 | return new Course(name, points);
43 | }
44 |
45 | private CoursePoint wayPointToCoursePoint(WptType wptType) {
46 | return new CoursePoint(
47 | Math.toRadians(wptType.getLat().doubleValue()),
48 | Math.toRadians(wptType.getLon().doubleValue()),
49 | wptType.getEle()==null?0d:wptType.getEle().doubleValue());
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/java/com/johnpickup/app/javafx/TaskArguments.java:
--------------------------------------------------------------------------------
1 | package com.johnpickup.app.javafx;
2 |
3 | import java.io.File;
4 | import java.util.Map;
5 |
6 | public class TaskArguments {
7 | private final File inputFile;
8 | private final File outputDir;
9 | private final Map options;
10 |
11 | TaskArguments(File inputFile, File outputDir, Map options) {
12 | this.inputFile = inputFile;
13 | this.outputDir = outputDir;
14 | this.options = options;
15 | }
16 |
17 | public static TaskArgumentsBuilder builder() {
18 | return new TaskArgumentsBuilder();
19 | }
20 |
21 | public File getInputFile() {
22 | return this.inputFile;
23 | }
24 |
25 | public File getOutputDir() {
26 | return this.outputDir;
27 | }
28 |
29 | public Map getOptions() {
30 | return this.options;
31 | }
32 |
33 | public String toString() {
34 | return "TaskArguments(inputFile=" + this.getInputFile() + ", outputDir=" + this.getOutputDir() + ", options=" + this.getOptions() + ")";
35 | }
36 |
37 | public static class TaskArgumentsBuilder {
38 | private File inputFile;
39 | private File outputDir;
40 | private Map options;
41 |
42 | TaskArgumentsBuilder() {
43 | }
44 |
45 | public TaskArgumentsBuilder inputFile(File inputFile) {
46 | this.inputFile = inputFile;
47 | return this;
48 | }
49 |
50 | public TaskArgumentsBuilder outputDir(File outputDir) {
51 | this.outputDir = outputDir;
52 | return this;
53 | }
54 |
55 | public TaskArgumentsBuilder options(Map options) {
56 | this.options = options;
57 | return this;
58 | }
59 |
60 | public TaskArguments build() {
61 | return new TaskArguments(inputFile, outputDir, options);
62 | }
63 |
64 | public String toString() {
65 | return "TaskArguments.TaskArgumentsBuilder(inputFile=" + this.inputFile + ", outputDir=" + this.outputDir + ", options=" + this.options + ")";
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/common/src/main/java/com/johnpickup/garmin/common/unit/Pace.java:
--------------------------------------------------------------------------------
1 | package com.johnpickup.garmin.common.unit;
2 |
3 | import java.util.Objects;
4 |
5 | /**
6 | * Encapsulation of various pace/speed standards with human-readable toString plus a conversion to Garmin units
7 | */
8 | public class Pace {
9 | private final double value;
10 | private final PaceUnit unit;
11 |
12 | public Pace(double value, PaceUnit 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 toGarminPace() {
23 | // Garmin pace units are in millimetres per second
24 | return switch (unit) {
25 | case KILOMETRE_PER_HOUR -> (long) (value / 3.6 * 1000);
26 | case MILE_PER_HOUR -> (long) (value / 3.6 * 1609);
27 | case MIN_PER_MILE -> (long) (60 / value / 3.6 * 1609);
28 | case MIN_PER_KILOMETRE -> (long) (60 / value / 3.6 * 1000);
29 | };
30 | }
31 |
32 | public String toValueString() {
33 |
34 | switch (unit) {
35 | case KILOMETRE_PER_HOUR:
36 | case MILE_PER_HOUR:
37 | if(value == (long) value)
38 | return String.format("%d",(long)value);
39 | else
40 | return String.format("%s", value);
41 | case MIN_PER_MILE:
42 | case MIN_PER_KILOMETRE:
43 | return String.format("%d:%02d", (long)value, (long)((value - (long)value)*60));
44 | }
45 | return null;
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 | Pace pace = (Pace) o;
53 | return Double.compare(pace.value, value) == 0 && unit == pace.unit;
54 | }
55 |
56 | @Override
57 | public int hashCode() {
58 | return Objects.hash(value, unit);
59 | }
60 |
61 | protected boolean canEqual(final Object other) {
62 | return other instanceof Pace;
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/main/java/com/johnpickup/app/garmin/workout/OpenHeartRateWorkoutStep.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.HeartRateTarget;
8 |
9 | import java.util.Collections;
10 | import java.util.List;
11 | import java.util.Objects;
12 |
13 | /**
14 | * Simple workout step that's open with a heart rate target
15 | */
16 | public class OpenHeartRateWorkoutStep extends WorkoutStep {
17 | private final HeartRateTarget heartRateTarget;
18 |
19 | public OpenHeartRateWorkoutStep(Intensity intensity, HeartRateTarget heartRateTarget) {
20 | super(intensity);
21 | this.heartRateTarget = heartRateTarget;
22 | }
23 |
24 | @Override
25 | public String getName() {
26 | return "Open " + heartRateTarget.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.HEART_RATE);
35 | step.setTargetValue(heartRateTarget.getTargetValue());
36 | step.setMessageIndex(generateWorkoutStepIndex());
37 | step.setCustomTargetValueLow(heartRateTarget.getGarminLow());
38 | step.setCustomTargetValueHigh(heartRateTarget.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 | OpenHeartRateWorkoutStep that = (OpenHeartRateWorkoutStep) o;
49 | return Objects.equals(heartRateTarget, that.heartRateTarget);
50 | }
51 |
52 | @Override
53 | public int hashCode() {
54 | return Objects.hash(heartRateTarget);
55 | }
56 |
57 | protected boolean canEqual(final Object other) {
58 | return other instanceof OpenHeartRateWorkoutStep;
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/java/com/johnpickup/app/calendar/ScheduleIcalWriter.java:
--------------------------------------------------------------------------------
1 | package com.johnpickup.app.calendar;
2 |
3 | import biweekly.Biweekly;
4 | import biweekly.ICalendar;
5 | import biweekly.component.VEvent;
6 | import biweekly.property.Summary;
7 |
8 | import java.io.File;
9 | import java.io.FileWriter;
10 | import java.time.LocalDate;
11 | import java.time.ZoneId;
12 | import java.util.ArrayList;
13 | import java.util.Comparator;
14 | import java.util.List;
15 |
16 | public class ScheduleIcalWriter implements ScheduleWriter {
17 | private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ScheduleIcalWriter.class);
18 | private final File outputFile;
19 | private ICalendar iCalendar;
20 |
21 | public ScheduleIcalWriter(File outputFile) {
22 | this.outputFile = outputFile;
23 | }
24 |
25 | @Override
26 | public void write(Schedule schedule) throws Exception {
27 | iCalendar = new ICalendar();
28 |
29 | List workouts = new ArrayList<>(schedule.getWorkouts());
30 | workouts.sort(Comparator.comparing(ScheduledWorkout::getDate));
31 |
32 | for (ScheduledWorkout workout : workouts) {
33 | log.info(workout.getName());
34 | writeWorkout(workout);
35 | }
36 | log.info("Done creating iCal");
37 |
38 | String iCalStr = Biweekly.write(iCalendar).go();
39 |
40 | FileWriter writer = new FileWriter(outputFile);
41 | writer.write(iCalStr);
42 | writer.close();
43 |
44 | log.info("Done writing iCal to {}", outputFile.getAbsolutePath());
45 | }
46 |
47 | private void writeWorkout(ScheduledWorkout workout) {
48 | LocalDate workoutDate = workout.getDate();
49 | VEvent event = new VEvent();
50 | Summary summary = event.setSummary(workout.getName());
51 | summary.setLanguage("en-uk");
52 | java.util.Date date = java.util.Date.from(workoutDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
53 | event.setDateStart(date, false);
54 | //Duration duration = new Duration.Builder().days(1).build();
55 | //event.setDuration(duration);
56 | event.setDescription(workout.getDescription());
57 |
58 | iCalendar.addEvent(event);
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/java/com/johnpickup/app/calendar/ProgrammeReader.java:
--------------------------------------------------------------------------------
1 | package com.johnpickup.app.calendar;
2 |
3 | import com.johnpickup.app.excel.ExcelUtils;
4 | import org.apache.poi.hssf.usermodel.HSSFWorkbook;
5 | import org.apache.poi.ss.usermodel.*;
6 |
7 | import java.io.FileInputStream;
8 | import java.io.IOException;
9 |
10 | public class ProgrammeReader {
11 |
12 | private String filename;
13 | private int rowIdx;
14 | private int nameIndex = -1;
15 | private int descriptionIndex = -1;
16 | private int offsetIndex = -1;
17 |
18 | public ProgrammeReader(String filename) {
19 | this.filename = filename;
20 | }
21 |
22 | public Programme read() throws IOException {
23 | Programme result = new Programme();
24 | FileInputStream inputFile = new FileInputStream(filename);
25 |
26 | Workbook wb = new HSSFWorkbook(inputFile);
27 |
28 | Sheet sheet = wb.getSheet("Schedule");
29 |
30 | rowIdx = 0;
31 |
32 | for (Row row : sheet) {
33 | if (rowIdx++ == 0) {
34 | readHeader(row);
35 | } else {
36 | PlannedWorkout workout = readWorkout(row);
37 | if (workout != null) {
38 | result.addWorkout(workout);
39 | }
40 | }
41 | }
42 | wb.close();
43 |
44 | return result;
45 | }
46 |
47 | private PlannedWorkout readWorkout(Row row) {
48 | String name = ExcelUtils.readStringValue(row, nameIndex);
49 | String description = ExcelUtils.readStringValue(row, descriptionIndex);
50 | Integer offset = ExcelUtils.readIntValue(row, offsetIndex);
51 | if (name == null || description == null || offset == null) return null;
52 | return new PlannedWorkout(name, description, offset);
53 | }
54 |
55 | private void readHeader(Row row) {
56 | for (Cell cell : row) {
57 | if (cell.getCellType() != CellType.STRING) continue;
58 |
59 | if ("Workout".equals(cell.getStringCellValue())) nameIndex = cell.getColumnIndex();
60 | if ("Description".equals(cell.getStringCellValue())) descriptionIndex = cell.getColumnIndex();
61 | if ("Offset".equals(cell.getStringCellValue())) offsetIndex = cell.getColumnIndex();
62 | }
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/common/src/main/java/com/johnpickup/garmin/common/unit/PaceTarget.java:
--------------------------------------------------------------------------------
1 | package com.johnpickup.garmin.common.unit;
2 |
3 | import java.util.Objects;
4 |
5 | /**
6 | * Pace target - a minimum and maximum pace; doesn't really care about which is which (min/max pace vs min/max speed)
7 | * and so returns the appropriate one in the Garmin Low and High methods
8 | */
9 | public class PaceTarget {
10 | private final String name;
11 | private final Pace maxPace;
12 | private final Pace minPace;
13 |
14 | public PaceTarget(double min, double max, PaceUnit unit) {
15 | this(null, min, max, unit);
16 | }
17 |
18 | public PaceTarget(String name, double min, double max, PaceUnit unit) {
19 | this.name = name;
20 | this.minPace = new Pace(min, unit);
21 | this.maxPace = new Pace(max, unit);
22 | }
23 |
24 | @Override
25 | public String toString() {
26 | return Objects.requireNonNullElseGet(name, () -> minPace.toValueString() + "-" + maxPace.toString());
27 | }
28 |
29 | public Long getGarminLow() {
30 | if (minPace.toGarminPace() < maxPace.toGarminPace())
31 | return minPace.toGarminPace();
32 | else
33 | return maxPace.toGarminPace();
34 | }
35 |
36 | public Long getGarminHigh() {
37 | if (minPace.toGarminPace() < maxPace.toGarminPace())
38 | return maxPace.toGarminPace();
39 | else
40 | return minPace.toGarminPace();
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 | PaceTarget that = (PaceTarget) o;
48 | return Objects.equals(name, that.name) && Objects.equals(maxPace, that.maxPace) && Objects.equals(minPace, that.minPace);
49 | }
50 |
51 | @Override
52 | public int hashCode() {
53 | return Objects.hash(name, maxPace, minPace);
54 | }
55 |
56 | protected boolean canEqual(final Object other) {
57 | return other instanceof PaceTarget;
58 | }
59 |
60 | public String getName() {
61 | return this.name;
62 | }
63 |
64 | public Pace getMaxPace() {
65 | return this.maxPace;
66 | }
67 |
68 | public Pace getMinPace() {
69 | return this.minPace;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/app/src/main/java/com/johnpickup/app/garmin/workout/TimePaceWorkoutStep.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 | import com.johnpickup.garmin.common.unit.Time;
9 |
10 | import java.util.Collections;
11 | import java.util.List;
12 | import java.util.Objects;
13 |
14 | /**
15 | * Simple workout that lasts a specific distance with a pace target
16 | */
17 | public class TimePaceWorkoutStep extends WorkoutStep {
18 | private final Time time;
19 | private final PaceTarget paceTarget;
20 |
21 | public TimePaceWorkoutStep(Intensity intensity, Time time, PaceTarget paceTarget) {
22 | super(intensity);
23 | this.time = time;
24 | this.paceTarget = paceTarget;
25 | }
26 |
27 | @Override
28 | public String getName() {
29 | return time.toString() + " " + paceTarget.toString();
30 | }
31 |
32 | @Override
33 | public List generateWorkoutSteps() {
34 | WorkoutStepMesg step = new WorkoutStepMesg();
35 | step.setIntensity(intensity);
36 | step.setDurationType(WktStepDuration.TIME);
37 | step.setDurationDistance(time.toGarminTime());
38 | step.setTargetType(WktStepTarget.SPEED);
39 | step.setTargetValue(0L);
40 | step.setMessageIndex(generateWorkoutStepIndex());
41 | step.setCustomTargetValueLow(paceTarget.getGarminLow());
42 | step.setCustomTargetValueHigh(paceTarget.getGarminHigh());
43 | step.setNotes(nameWithIntensity());
44 |
45 | return Collections.singletonList(step);
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 | TimePaceWorkoutStep that = (TimePaceWorkoutStep) o;
53 | return Objects.equals(time, that.time) && Objects.equals(paceTarget, that.paceTarget);
54 | }
55 |
56 | @Override
57 | public int hashCode() {
58 | return Objects.hash(time, paceTarget);
59 | }
60 |
61 | protected boolean canEqual(final Object other) {
62 | return other instanceof TimePaceWorkoutStep;
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/main/java/com/johnpickup/app/garmin/workout/TimePowerWorkoutStep.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.PowerTarget;
8 | import com.johnpickup.garmin.common.unit.Time;
9 |
10 | import java.util.Collections;
11 | import java.util.List;
12 | import java.util.Objects;
13 |
14 | /**
15 | * Simple workout that lasts a specific distance with a pace target
16 | */
17 | public class TimePowerWorkoutStep extends WorkoutStep {
18 | private final Time time;
19 | private final PowerTarget powerTarget;
20 |
21 | public TimePowerWorkoutStep(Intensity intensity, Time time, PowerTarget powerTarget) {
22 | super(intensity);
23 | this.time = time;
24 | this.powerTarget = powerTarget;
25 | }
26 |
27 | @Override
28 | public String getName() {
29 | return time.toString() + " " + powerTarget.toString();
30 | }
31 |
32 | @Override
33 | public List generateWorkoutSteps() {
34 | WorkoutStepMesg step = new WorkoutStepMesg();
35 | step.setIntensity(intensity);
36 | step.setDurationType(WktStepDuration.TIME);
37 | step.setDurationDistance(time.toGarminTime());
38 | step.setTargetType(WktStepTarget.POWER);
39 | step.setTargetValue(powerTarget.getTargetValue());
40 | step.setMessageIndex(generateWorkoutStepIndex());
41 | step.setCustomTargetValueLow(powerTarget.getGarminLow());
42 | step.setCustomTargetValueHigh(powerTarget.getGarminHigh());
43 | step.setNotes(nameWithIntensity());
44 | return Collections.singletonList(step);
45 | }
46 |
47 | @Override
48 | public boolean equals(Object o) {
49 | if (this == o) return true;
50 | if (o == null || getClass() != o.getClass()) return false;
51 | TimePowerWorkoutStep that = (TimePowerWorkoutStep) o;
52 | return Objects.equals(time, that.time) && Objects.equals(powerTarget, that.powerTarget);
53 | }
54 |
55 | @Override
56 | public int hashCode() {
57 | return Objects.hash(time, powerTarget);
58 | }
59 |
60 | protected boolean canEqual(final Object other) {
61 | return other instanceof TimePowerWorkoutStep;
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/com/johnpickup/app/garmin/workout/DistancePaceWorkoutStep.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 | import com.johnpickup.garmin.common.unit.PaceTarget;
9 |
10 | import java.util.Collections;
11 | import java.util.List;
12 | import java.util.Objects;
13 |
14 | /**
15 | * Simple workout that lasts a specific distance with a pace target
16 | */
17 | public class DistancePaceWorkoutStep extends WorkoutStep {
18 | private final Distance distance;
19 | private final PaceTarget paceTarget;
20 |
21 | public DistancePaceWorkoutStep(Intensity intensity, Distance distance, PaceTarget paceTarget) {
22 | super(intensity);
23 | this.distance = distance;
24 | this.paceTarget = paceTarget;
25 | }
26 |
27 | @Override
28 | public String getName() {
29 | return distance.toString() + " " + paceTarget.toString();
30 | }
31 |
32 | @Override
33 | public List generateWorkoutSteps() {
34 | WorkoutStepMesg step = new WorkoutStepMesg();
35 | step.setIntensity(intensity);
36 | step.setDurationType(WktStepDuration.DISTANCE);
37 | step.setDurationDistance(distance.toGarminDistance());
38 | step.setTargetType(WktStepTarget.SPEED);
39 | step.setTargetValue(0L);
40 | step.setMessageIndex(generateWorkoutStepIndex());
41 | step.setCustomTargetValueLow(paceTarget.getGarminLow());
42 | step.setCustomTargetValueHigh(paceTarget.getGarminHigh());
43 | step.setNotes(nameWithIntensity());
44 |
45 | return Collections.singletonList(step);
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 | DistancePaceWorkoutStep that = (DistancePaceWorkoutStep) o;
53 | return Objects.equals(distance, that.distance) && Objects.equals(paceTarget, that.paceTarget);
54 | }
55 |
56 | @Override
57 | public int hashCode() {
58 | return Objects.hash(distance, paceTarget);
59 | }
60 |
61 | protected boolean canEqual(final Object other) {
62 | return other instanceof DistancePaceWorkoutStep;
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/main/java/com/johnpickup/app/CalendarScheduleGenerator.java:
--------------------------------------------------------------------------------
1 | package com.johnpickup.app;
2 |
3 | import com.johnpickup.app.calendar.Schedule;
4 | import com.johnpickup.app.calendar.ScheduleIcalWriter;
5 | import com.johnpickup.app.calendar.WorkoutScheduleConverter;
6 | import com.johnpickup.app.excel.ExcelWorkoutScheduleReader;
7 | import com.johnpickup.garmin.parser.WorkoutSchedule;
8 |
9 | import java.io.File;
10 |
11 |
12 | /**
13 | * Simple class with command-line interface that takes an Excel definition of a workout schedule and produces
14 | * in iCal file that can be imported into a calendar application
15 | */
16 | public class CalendarScheduleGenerator {
17 | private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CalendarScheduleGenerator.class);
18 |
19 | public static void main(String[] args) {
20 | try {
21 | CalendarScheduleGenerator instance = new CalendarScheduleGenerator();
22 | instance.generate(new File(args[0]), new File("."));
23 | } catch (Exception e) {
24 | log.error("Failed to generate iCal schedule", e);
25 | }
26 | }
27 |
28 | public void generate(File inputFile, File outputDir) {
29 | try {
30 | log.info("Converting {}", inputFile.getPath());
31 | String strippedName = inputFile.getName();
32 | if (strippedName.contains(".xls")) {
33 | strippedName = strippedName.substring(0, strippedName.indexOf(".xls"));
34 | }
35 | File outputFile = new File(outputDir, strippedName+".ics");
36 |
37 | log.info("Writing output to {}", outputFile.getPath());
38 | ExcelWorkoutScheduleReader reader = new ExcelWorkoutScheduleReader();
39 | log.info("Reading workout schedule");
40 | WorkoutSchedule workoutSchedule = reader.read(inputFile);
41 |
42 | log.info("Converting workout schedule");
43 | WorkoutScheduleConverter converter = new WorkoutScheduleConverter();
44 | Schedule schedule = converter.convert(workoutSchedule);
45 |
46 | log.info("Writing iCal schedule");
47 | ScheduleIcalWriter icalWriter = new ScheduleIcalWriter(outputFile);
48 | icalWriter.write(schedule);
49 | }
50 | catch (Exception e) {
51 | log.error("Error converting {}", inputFile.getPath());
52 | log.error(e.getMessage());
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/com/johnpickup/app/garmin/workout/TimeHeartRateWorkoutStep.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.HeartRateTarget;
8 | import com.johnpickup.garmin.common.unit.Time;
9 |
10 | import java.util.Collections;
11 | import java.util.List;
12 | import java.util.Objects;
13 |
14 | /**
15 | * Simple workout that lasts a specific distance with a pace target
16 | */
17 | public class TimeHeartRateWorkoutStep extends WorkoutStep {
18 | private final Time time;
19 | private final HeartRateTarget heartRateTarget;
20 |
21 | public TimeHeartRateWorkoutStep(Intensity intensity, Time time, HeartRateTarget heartRateTarget) {
22 | super(intensity);
23 | this.time = time;
24 | this.heartRateTarget = heartRateTarget;
25 | }
26 |
27 | @Override
28 | public String getName() {
29 | return time.toString() + " " + heartRateTarget.toString();
30 | }
31 |
32 | @Override
33 | public List generateWorkoutSteps() {
34 | WorkoutStepMesg step = new WorkoutStepMesg();
35 | step.setIntensity(intensity);
36 | step.setDurationType(WktStepDuration.TIME);
37 | step.setDurationDistance(time.toGarminTime());
38 | step.setTargetType(WktStepTarget.HEART_RATE);
39 | step.setTargetValue(heartRateTarget.getTargetValue());
40 | step.setMessageIndex(generateWorkoutStepIndex());
41 | step.setCustomTargetValueLow(heartRateTarget.getGarminLow());
42 | step.setCustomTargetValueHigh(heartRateTarget.getGarminHigh());
43 | step.setNotes(nameWithIntensity());
44 | return Collections.singletonList(step);
45 | }
46 |
47 | @Override
48 | public boolean equals(Object o) {
49 | if (this == o) return true;
50 | if (o == null || getClass() != o.getClass()) return false;
51 | TimeHeartRateWorkoutStep that = (TimeHeartRateWorkoutStep) o;
52 | return Objects.equals(time, that.time) && Objects.equals(heartRateTarget, that.heartRateTarget);
53 | }
54 |
55 | @Override
56 | public int hashCode() {
57 | return Objects.hash(time, heartRateTarget);
58 | }
59 |
60 | protected boolean canEqual(final Object other) {
61 | return other instanceof TimeHeartRateWorkoutStep;
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/com/johnpickup/app/garmin/workout/DistancePowerWorkoutStep.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 | import com.johnpickup.garmin.common.unit.PowerTarget;
9 |
10 | import java.util.Collections;
11 | import java.util.List;
12 | import java.util.Objects;
13 |
14 | /**
15 | * Simple workout that lasts a specific distance with a heart rate target
16 | */
17 | public class DistancePowerWorkoutStep extends WorkoutStep {
18 | private final Distance distance;
19 | private final PowerTarget powerTarget;
20 |
21 | public DistancePowerWorkoutStep(Intensity intensity, Distance distance, PowerTarget powerTarget) {
22 | super(intensity);
23 | this.distance = distance;
24 | this.powerTarget = powerTarget;
25 | }
26 |
27 | @Override
28 | public String getName() {
29 | return distance.toString() + " " + powerTarget.toString();
30 | }
31 |
32 | @Override
33 | public List generateWorkoutSteps() {
34 | WorkoutStepMesg step = new WorkoutStepMesg();
35 | step.setIntensity(intensity);
36 | step.setDurationType(WktStepDuration.DISTANCE);
37 | step.setDurationDistance(distance.toGarminDistance());
38 | step.setTargetType(WktStepTarget.POWER);
39 | step.setTargetValue(powerTarget.getTargetValue());
40 | step.setMessageIndex(generateWorkoutStepIndex());
41 | step.setCustomTargetValueLow(powerTarget.getGarminLow());
42 | step.setCustomTargetValueHigh(powerTarget.getGarminHigh());
43 | step.setNotes(nameWithIntensity());
44 |
45 | return Collections.singletonList(step);
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 | DistancePowerWorkoutStep that = (DistancePowerWorkoutStep) o;
53 | return Objects.equals(distance, that.distance) && Objects.equals(powerTarget, that.powerTarget);
54 | }
55 |
56 | @Override
57 | public int hashCode() {
58 | return Objects.hash(distance, powerTarget);
59 | }
60 |
61 | protected boolean canEqual(final Object other) {
62 | return other instanceof DistancePowerWorkoutStep;
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/main/java/com/johnpickup/app/calendar/ScheduleExcelWriter.java:
--------------------------------------------------------------------------------
1 | package com.johnpickup.app.calendar;
2 |
3 | import org.apache.poi.hssf.usermodel.HSSFWorkbook;
4 | import org.apache.poi.ss.usermodel.*;
5 |
6 | import java.io.FileOutputStream;
7 | import java.io.IOException;
8 | import java.time.ZoneId;
9 | import java.util.ArrayList;
10 | import java.util.Comparator;
11 | import java.util.List;
12 |
13 | public class ScheduleExcelWriter implements ScheduleWriter {
14 |
15 | private String filename;
16 | private int rowIdx;
17 | private CellStyle dateStyle;
18 |
19 | public ScheduleExcelWriter(String filename) {
20 | this.filename = filename;
21 | }
22 |
23 | public void write(Schedule schedule) throws IOException {
24 | Workbook wb = new HSSFWorkbook();
25 | initialiseWorkbook(wb);
26 |
27 | FileOutputStream fileOut = new FileOutputStream(filename);
28 |
29 | Sheet sheet = wb.createSheet("Programme");
30 | writeHeader(sheet);
31 |
32 | rowIdx = 1;
33 |
34 | List workouts = new ArrayList<>(schedule.getWorkouts());
35 | workouts.sort(Comparator.comparing(ScheduledWorkout::getDate));
36 |
37 | for (ScheduledWorkout workout : workouts) {
38 | writeWorkout(sheet, workout);
39 | rowIdx++;
40 | }
41 |
42 | wb.write(fileOut);
43 | fileOut.close();
44 | }
45 |
46 | private void initialiseWorkbook(Workbook workbook) {
47 | CreationHelper createHelper = workbook.getCreationHelper();
48 | dateStyle = workbook.createCellStyle();
49 | dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("dd/mm/yyyy"));
50 | }
51 |
52 | private void writeWorkout(Sheet sheet, ScheduledWorkout workout) {
53 | Row row = sheet.createRow(rowIdx);
54 | row.createCell(0).setCellValue(workout.getName());
55 | row.createCell(1).setCellValue(workout.getDescription());
56 |
57 | java.util.Date dateVal = java.util.Date.from(workout.getDate().atStartOfDay(ZoneId.systemDefault()).toInstant());
58 | Cell cell = row.createCell(2);
59 | cell.setCellValue(dateVal);
60 | cell.setCellStyle(dateStyle);
61 | }
62 |
63 | private void writeHeader(Sheet sheet) {
64 | Row row = sheet.createRow(rowIdx);
65 | row.createCell(0).setCellValue("Name");
66 | row.createCell(1).setCellValue("Description");
67 | row.createCell(2).setCellValue("Date");
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/test/java/com/johnpickup/app/garmin/unit/PaceTest.java:
--------------------------------------------------------------------------------
1 | package com.johnpickup.app.garmin.unit;
2 |
3 | import com.johnpickup.garmin.common.unit.Pace;
4 | import com.johnpickup.garmin.common.unit.PaceUnit;
5 | import org.junit.Test;
6 |
7 | import static org.junit.Assert.assertEquals;
8 |
9 | /**
10 | * Created by john on 30/12/2016.
11 | */
12 | public class PaceTest {
13 | @Test
14 | public void minPerKmToGarminPace() throws Exception {
15 | Pace pace = new Pace(5.0, PaceUnit.MIN_PER_KILOMETRE);
16 | Long actual = pace.toGarminPace();
17 | Long expected = 3333L;
18 | assertEquals(expected, actual);
19 | }
20 |
21 | @Test
22 | public void minPerMileToGarminPace() throws Exception {
23 | Pace pace = new Pace(8.0, PaceUnit.MIN_PER_MILE);
24 | Long actual = pace.toGarminPace();
25 | Long expected = 3352L;
26 | assertEquals(expected, actual);
27 | }
28 |
29 | @Test
30 | public void kphToGarminPace() throws Exception {
31 | Pace pace = new Pace(12, PaceUnit.KILOMETRE_PER_HOUR);
32 | Long actual = pace.toGarminPace();
33 | Long expected = 3333L;
34 | assertEquals(expected, actual);
35 | }
36 |
37 | @Test
38 | public void mphToGarminPace() throws Exception {
39 | Pace pace = new Pace(6, PaceUnit.MILE_PER_HOUR);
40 | Long actual = pace.toGarminPace();
41 | Long expected = 2681L;
42 | assertEquals(expected, actual);
43 | }
44 |
45 | @Test
46 | public void toStringTestMinPerMile() throws Exception {
47 | Pace pace = new Pace(6, PaceUnit.MIN_PER_MILE);
48 | assertEquals("6:00/mi", pace.toString());
49 | }
50 |
51 | @Test
52 | public void toStringTestMinPerMile2() throws Exception {
53 | Pace pace = new Pace(6.5, PaceUnit.MIN_PER_MILE);
54 | assertEquals("6:30/mi", pace.toString());
55 | }
56 |
57 | @Test
58 | public void toStringTestMinPerKm() throws Exception {
59 | Pace pace = new Pace(4.5, PaceUnit.MIN_PER_KILOMETRE);
60 | assertEquals("4:30/km", pace.toString());
61 | }
62 |
63 | @Test
64 | public void toStringTestKph() throws Exception {
65 | Pace pace = new Pace(5, PaceUnit.KILOMETRE_PER_HOUR);
66 | assertEquals("5kph", pace.toString());
67 | }
68 |
69 | @Test
70 | public void toStringTestMph() throws Exception {
71 | Pace pace = new Pace(5.6, PaceUnit.MILE_PER_HOUR);
72 | assertEquals("5.6MPH", pace.toString());
73 | }
74 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/johnpickup/app/GarminRouteGenerator.java:
--------------------------------------------------------------------------------
1 | package com.johnpickup.app;
2 |
3 | import com.johnpickup.app.garmin.converter.CourseConverter;
4 | import com.johnpickup.app.garmin.fit.FitSaver;
5 | import com.johnpickup.app.garmin.route.Course;
6 | import com.johnpickup.app.gpx.GpxReader;
7 | import com.johnpickup.gpx.GpxType;
8 | import jakarta.xml.bind.JAXBException;
9 | import org.apache.commons.io.FilenameUtils;
10 |
11 | import java.io.File;
12 | import java.io.FileNotFoundException;
13 |
14 | public class GarminRouteGenerator {
15 | private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GarminRouteGenerator.class);
16 |
17 | private final GpxReader gpxReader = new GpxReader();
18 | private final CourseConverter courseConverter = new CourseConverter();
19 |
20 | public static void main(String[] args) {
21 | if (args.length == 0) {
22 | runGui();
23 | }
24 |
25 | try {
26 | GarminRouteGenerator garminRouteGenerator = new GarminRouteGenerator();
27 | for (String arg : args) {
28 | garminRouteGenerator.convert(arg, false);
29 | }
30 | }
31 | catch (Exception ex) {
32 | log.error(ex.getMessage());
33 | }
34 |
35 | }
36 |
37 | private static void runGui() {
38 | // TODO RouteConverterForm.display();
39 | }
40 |
41 | private void convert(String inputFilename, boolean reverse) throws JAXBException, FileNotFoundException {
42 | String outputDir = FilenameUtils.getPath(inputFilename);
43 | convert(new File(inputFilename), new File(outputDir), reverse);
44 | }
45 |
46 | public void convert(File inputFile, File outputFile, boolean reverse) throws JAXBException, FileNotFoundException {
47 | if (outputFile.isDirectory()) {
48 | outputFile = new File(outputFile, FilenameUtils.getBaseName(inputFile.getName()) + ".fit");
49 | }
50 |
51 | log.info("Converting {}", inputFile);
52 | GpxType gpxType = gpxReader.readGpxFile(inputFile);
53 | Course convertedCourse = courseConverter.convert(gpxType);
54 | log.info("Converted GPX '{}' to a course containing {} points", convertedCourse.getName(), convertedCourse.size());
55 | if (reverse) {
56 | log.info("Reversing route");
57 | convertedCourse.reverse();
58 | }
59 | log.info("Saving route to {}", outputFile);
60 | FitSaver.save(convertedCourse, outputFile);
61 | log.info("Saved to {}", outputFile);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/main/java/com/johnpickup/app/garmin/workout/DistanceHeartRateWorkoutStep.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 | import com.johnpickup.garmin.common.unit.HeartRateTarget;
9 |
10 | import java.util.Collections;
11 | import java.util.List;
12 | import java.util.Objects;
13 |
14 | /**
15 | * Simple workout that lasts a specific distance with a heart rate target
16 | */
17 | public class DistanceHeartRateWorkoutStep extends WorkoutStep {
18 | private final Distance distance;
19 | private final HeartRateTarget heartRateTarget;
20 |
21 | public DistanceHeartRateWorkoutStep(Intensity intensity, Distance distance, HeartRateTarget heartRateTarget) {
22 | super(intensity);
23 | this.distance = distance;
24 | this.heartRateTarget = heartRateTarget;
25 | }
26 |
27 | @Override
28 | public String getName() {
29 | return distance.toString() + " " + heartRateTarget.toString();
30 | }
31 |
32 | @Override
33 | public List generateWorkoutSteps() {
34 | WorkoutStepMesg step = new WorkoutStepMesg();
35 | step.setIntensity(intensity);
36 | step.setDurationType(WktStepDuration.DISTANCE);
37 | step.setDurationDistance(distance.toGarminDistance());
38 | step.setTargetType(WktStepTarget.HEART_RATE);
39 | step.setTargetValue(heartRateTarget.getTargetValue());
40 | step.setMessageIndex(generateWorkoutStepIndex());
41 | step.setCustomTargetValueLow(heartRateTarget.getGarminLow());
42 | step.setCustomTargetValueHigh(heartRateTarget.getGarminHigh());
43 | step.setNotes(nameWithIntensity());
44 |
45 | return Collections.singletonList(step);
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 | DistanceHeartRateWorkoutStep that = (DistanceHeartRateWorkoutStep) o;
53 | return Objects.equals(distance, that.distance) && Objects.equals(heartRateTarget, that.heartRateTarget);
54 | }
55 |
56 | @Override
57 | public int hashCode() {
58 | return Objects.hash(distance, heartRateTarget);
59 | }
60 |
61 | protected boolean canEqual(final Object other) {
62 | return other instanceof DistanceHeartRateWorkoutStep;
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------