diff --git a/.github/workflows/maven-publish-linux.yml b/.github/workflows/maven-publish-linux.yml
new file mode 100644
index 0000000..62d94bc
--- /dev/null
+++ b/.github/workflows/maven-publish-linux.yml
@@ -0,0 +1,35 @@
+name: Maven Package for Linux
+
+#on:
+# release:
+# types: [created]
+on: [push]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up JDK 23
+ uses: actions/setup-java@v4
+ with:
+ java-version: '23'
+ distribution: 'oracle'
+ server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
+ settings-path: ${{ github.workspace }} # location for the settings.xml file
+
+ - name: Build with Maven
+ run: mvn install -s $GITHUB_WORKSPACE/settings.xml
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+
+ # 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
+ # 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
+ - name: Publish to GitHub Packages Apache Maven
+ run: mvn deploy -s $GITHUB_WORKSPACE/settings.xml || echo "Ignoring non-zero result"
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
diff --git a/.github/workflows/maven-publish-windows.yml b/.github/workflows/maven-publish-windows.yml
new file mode 100644
index 0000000..9e604a7
--- /dev/null
+++ b/.github/workflows/maven-publish-windows.yml
@@ -0,0 +1,34 @@
+name: Maven Package for Windows
+
+#on:
+# release:
+# types: [created]
+on: [push]
+
+jobs:
+ build:
+ runs-on: windows-latest
+ permissions:
+ contents: read
+ packages: write
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up JDK 23
+ uses: actions/setup-java@v4
+ with:
+ java-version: '23'
+ distribution: 'oracle'
+ server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
+ settings-path: ${{ github.workspace }} # location for the settings.xml file
+
+ - name: Build with Maven
+ run: mvn install -s ${{ github.workspace }}\settings.xml --file pom.xml
+
+ # 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
+ # 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
+ - name: Publish to GitHub Packages Apache Maven
+ run: mvn deploy -s ${{ github.workspace }}\settings.xml --file pom.xml || echo "Ignoring non-zero result" & exit 0
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index db77b62..6d4e20b 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -10,6 +10,7 @@
+
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index eed87f6..6b233fd 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -3,6 +3,8 @@
+
+
@@ -11,6 +13,7 @@
+
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
index 2ac1362..1572c3c 100644
--- a/.idea/jarRepositories.xml
+++ b/.idea/jarRepositories.xml
@@ -2,14 +2,9 @@
-
-
-
-
-
-
-
-
+
+
+
@@ -17,9 +12,9 @@
-
-
-
+
+
+
@@ -41,5 +36,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PowerWorkoutSchedule.xls b/PowerWorkoutSchedule.xls
new file mode 100644
index 0000000..86ebb24
Binary files /dev/null and b/PowerWorkoutSchedule.xls differ
diff --git a/README.md b/README.md
index 6fe718d..3553b62 100755
--- a/README.md
+++ b/README.md
@@ -6,11 +6,21 @@ A suite of tools for creating Garmin FIT files for workouts, schedules and cours
[Routes](./ROUTES.md)
## Building
-The application is a multi-module Maven project. It uses jlink and jpackage to produce installers for Windows and MacOS.
+The application is a multi-module Maven project. It uses jlink and jpackage to produce installers for Windows, MacOS and Linux.
The resulting installer files can be found in `app/target/installer` after the maven build has completed on the target platform.
-Alternatively the installers are checked into GitHub here: https://github.com/jpickup/GarminTools/tree/master/installer
+The main class is `app/src/main/java/com/johnpickup/app/javafx/AppLauncher.java` if you want to run this from an IDE.
-NOTE: for Windows the most recent version is available in a ZIP file as I've been unable to get compatible versions of
-the various tools required to build an MSI. Hopefully this will be fixed in the near future.
+## Installers
+The project now uses GitHub actions to build Windows and Linux installations. These can be found in the project's packages,
+here: https://github.com/jpickup/GarminTools/packages/
+Click on `com.johnpickup.garmin.bundle` and search for "windows.zip", "linux.zip" or "macos.zip", download and unpack.
+I need to figure out a better way to link to these.
+
+Previously the were installers are checked directly into GitHub but once the size exceeded the limit this was no longer
+suitable.
+
+NOTE:
+For Windows the most recent version is available in a ZIP file as I've been unable to get compatible versions of
+the various tools required to build an MSI.
diff --git a/WORKOUTS.md b/WORKOUTS.md
index 5df99ed..147836c 100755
--- a/WORKOUTS.md
+++ b/WORKOUTS.md
@@ -28,16 +28,18 @@ Workouts are defined as a series of steps, where a step is either a period of ti
that the step end when the lap button is pressed. For example 10 minutes or 400 metres. A step can also have a target
such as a pace or a heart rate. Steps are written as text, for example:
-| Workout | Description |
-|--------------------|-------------------------------------------------------------------------------|
-| `1mi` | A step of 1 mile |
-| `400m` | A step of 400 metres |
-| `Open` | An open step - ends when lap button pressed |
+| Workout | Description |
+|---------------------|-------------------------------------------------------------------------------|
+| `1mi` | A step of 1 mile |
+| `400m` | A step of 400 metres |
+| `Open` | An open step - ends when lap button pressed |
| `100m@4:00-5:00/km` | A step of 100 metres with a target pace between 4 and 5 minutes per kilometre |
-| `1mi>6mph` | 1 mile at a pace faster than 6 miles per hour |
+| `1mi>6mph` | 1 mile at a pace faster than 6 miles per hour |
| `Open@8:00-9:00/mi` | An open step with a pace target |
-| `800m@Z3` | 800 metres in heart rate Zone 3 |
-| `400m@160-180bpm` | 400 metres wth a heart rate between 160 and 180 beats per minute |
+| `800m@Z3` | 800 metres in heart rate Zone 3 |
+| `400m@160-180bpm` | 400 metres wth a heart rate between 160 and 180 beats per minute |
+| `30:00@PZ4` | 30 minutes at power zone 4 |
+| '20km@300-400W` | 20km at a power between 300 and 400 watts |
### Sequences of Steps
Steps can also be strung together with a `+` character, repeated using `*n` and grouped using brackets, for example:
@@ -59,10 +61,10 @@ More examples can be found in `ExampleWorkoutSchedule.xls`
## Units
### Distance
| Unit | Description | Example |
-| ---- | ----------- |---------|
-| m | Metre | 400m |
-| km | Kilometre | 5km |
-| mi | Mile | 26.2mi |
+|------|-------------|---------|
+| m | Metre | 400m |
+| km | Kilometre | 5km |
+| mi | Mile | 26.2mi |
### Intensity
Valid values for the intensity are:
@@ -97,6 +99,17 @@ Examples:
| 120-130bpm | between 120 and 130 beats per minute |
| Z3 | Zone 3 |
+### Power ranges
+These are similar to heart rate ranges where the units are watts (W) and the zones use a "PZ" prefix and a
+number between 1 and 7.
+
+Examples:
+
+| Power range | Description |
+|-------------|---------------------------|
+| 300-400W | between 300 and 400 watts |
+| PZ4 | Power zone 4 |
+
## Excel workbook
The app expects the input workbook to have specific named sheets and specific column headers within these. Any additional
sheets or columns are ignored.
@@ -107,32 +120,31 @@ sheets or columns are ignored.
| Workout | Named workouts in the workout language described above |
| Schedule | A schedule for workouts for specific dates |
-| Sheet | Column | Description |
-| ---- |-------------|-------------------------------------------------------------------------------------------|
-| Pace | Name | The name for a pace |
-| Pace | Value | The pace defined in workout language |
-| Workout | Name | The name for a workout |
-| Workout | Description | The workout language definition, may include named paces defined in the pace sheet |
-| Workout | Sport | The sport that this workout is for (if not present running is the default) |
-| Schedule | Date | The date for a specific workout |
-| Schedule | Workout | Either defined in the workout language or the name a named workout from the Workout sheet |
-| Schedule | Sport | The sport that this workout is for (if not present running is the default) |
+| Sheet | Column | Description |
+|----------|-------------|--------------------------------------------------------------------------------------------|
+| Pace | Name | The name for a pace |
+| Pace | Value | The pace defined in workout language |
+| Workout | Name | The name for a workout |
+| Workout | Description | The workout language definition, may include named paces defined in the pace sheet |
+| Workout | Sport | The sport that this workout is for (if not present running is the default) |
+| Schedule | Date | The date for a specific workout |
+| Schedule | Workout | Either defined in the workout language or the name a named workout from the Workout sheet |
+| Schedule | Sport | The sport that this workout is for (if not present running is the default) |
## Sport types
The sport type case be specified as a value in the spreadsheet under the Sport heading. The values supported are:
-| Sport | Description |
-| ----- | ----------------------------- |
-| Running | Running of any type |
-| Road running | Road running |
-| Trail running | Trail running |
-| Cycling | Cycling of any type |
-| Road cycling | Road cycling |
-| MTB | Mountain biking |
-| Swimming | Pool swimming |
-| Open water | Open water swimming |
+| Sport | Description |
+|---------------|-------------------------------|
+| Running | Running of any type |
+| Road running | Road running |
+| Trail running | Trail running |
+| Cycling | Cycling of any type |
+| Road cycling | Road cycling |
+| MTB | Mountain biking |
+| Swimming | Pool swimming |
+| Open water | Open water swimming |
## ANTLR4 Grammar
The grammar for the language is defined in `grammar/Workout.g4`, which is an ANTLR4 grammar that is used to generate
Java code.
-
diff --git a/app/build_app_debian.sh b/app/build_app_debian.sh
new file mode 100755
index 0000000..2028fd7
--- /dev/null
+++ b/app/build_app_debian.sh
@@ -0,0 +1,118 @@
+#!/bin/bash
+
+# ------ ENVIRONMENT --------------------------------------------------------
+# The script depends on various environment variables to exist in order to
+# run properly. The java version we want to use, the location of the java
+# binaries (java home), and the project version as defined inside the pom.xml
+# file, e.g. 1.0-SNAPSHOT.
+#
+# PROJECT_VERSION: version used in pom.xml, e.g. 1.0-SNAPSHOT
+# APP_VERSION: the application version, e.g. 1.0.0, shown in "about" dialog
+
+JAVA_VERSION=23
+MAIN_JAR="app-$PROJECT_VERSION.jar"
+
+# Set desired installer type: "dmg", "pkg", "deb", "app-image".
+INSTALLER_TYPE=app-image
+
+echo "java home: $JAVA_HOME"
+echo "project version: $PROJECT_VERSION"
+echo "app version: $APP_VERSION"
+echo "main JAR file: $MAIN_JAR"
+echo "Java home : $JAVA_HOME"
+
+# ------ SETUP DIRECTORIES AND FILES ----------------------------------------
+# Remove previously generated java runtime and installers. Copy all required
+# jar files into the input/libs folder.
+rm -rfd ./target/java-runtime/
+rm -rfd target/installer/
+
+mkdir -p target/installer/input/libs/
+
+cp target/libs/* target/installer/input/libs/
+cp target/${MAIN_JAR} target/installer/input/libs/
+
+# ------ REQUIRED MODULES ---------------------------------------------------
+# Use jlink to detect all modules that are required to run the application.
+# Starting point for the jdep analysis is the set of jars being used by the
+# application.
+
+echo "detecting required modules"
+$JAVA_HOME/bin/jdeps \
+ -v \
+ --multi-release ${JAVA_VERSION} \
+ --ignore-missing-deps \
+ --class-path "target/installer/input/libs/*" \
+ target/classes/com/johnpickup/app/javafx/MainForm.class
+detected_modules=`$JAVA_HOME/bin/jdeps \
+ -q \
+ --multi-release ${JAVA_VERSION} \
+ --ignore-missing-deps \
+ --print-module-deps \
+ --class-path "target/installer/input/libs/*" \
+ target/classes/com/johnpickup/app/javafx/MainForm.class`
+echo "detected modules: ${detected_modules}"
+
+
+# ------ MANUAL MODULES -----------------------------------------------------
+# jdk.crypto.ec has to be added manually bound via --bind-services or
+# otherwise HTTPS does not work.
+#
+# See: https://bugs.openjdk.java.net/browse/JDK-8221674
+#
+# In addition we need jdk.localedata if the application is localized.
+# This can be reduced to the actually needed locales via a jlink parameter,
+# e.g., --include-locales=en,de.
+#
+# Don't forget the leading ','!
+
+manual_modules=,jdk.crypto.ec,jdk.localedata
+echo "manual modules: ${manual_modules}"
+
+# ------ RUNTIME IMAGE ------------------------------------------------------
+# Use the jlink tool to create a runtime image for our application. We are
+# doing this in a separate step instead of letting jlink do the work as part
+# of the jpackage tool. This approach allows for finer configuration and also
+# works with dependencies that are not fully modularized, yet.
+echo "creating java runtime image"
+$JAVA_HOME/bin/jlink \
+ --strip-native-commands \
+ --no-header-files \
+ --bind-services \
+ --no-man-pages \
+ --strip-debug \
+ --add-modules "${detected_modules}${manual_modules}" \
+ --include-locales=en,de \
+ --output target/java-runtime
+
+# ------ PACKAGING ----------------------------------------------------------
+# In the end we will find the package inside the target/installer directory.
+echo "Creating installer of type $INSTALLER_TYPE"
+$JAVA_HOME/bin/jpackage \
+--type $INSTALLER_TYPE \
+--dest target/installer/ \
+--input target/installer/input/libs \
+--name garmintools \
+--main-class com.johnpickup.app.javafx.AppLauncher \
+--main-jar ${MAIN_JAR} \
+--java-options -Xmx2048m \
+--java-options '--sun-misc-unsafe-memory-access=allow' \
+--runtime-image target/java-runtime \
+--app-version ${APP_VERSION} \
+--vendor "John Pickup" \
+--copyright "Copyright © 2025 John Pickup" \
+
+DEB_PKG_ROOT=target/deb/garmintools
+rm -rf ${DEB_PKG_ROOT}
+mkdir -p ${DEB_PKG_ROOT}/opt
+mkdir -p ${DEB_PKG_ROOT}/usr/bin
+mkdir -p ${DEB_PKG_ROOT}/DEBIAN
+cp src/main/resources/control ${DEB_PKG_ROOT}/DEBIAN/
+mv target/installer/garmintools ${DEB_PKG_ROOT}/opt/
+cd ${DEB_PKG_ROOT}/usr/bin || exit
+ln -s ../../opt/garmintools/bin/garmintools garmintools
+cd - || exit
+cd target/deb || exit
+dpkg-deb -b garmintools
+cd - || exit
+cp target/deb/garmintools*deb ../installer
\ No newline at end of file
diff --git a/app/build_app_linux.sh b/app/build_app_linux.sh
new file mode 100755
index 0000000..8d74cb0
--- /dev/null
+++ b/app/build_app_linux.sh
@@ -0,0 +1,121 @@
+#!/bin/bash
+
+# ------ ENVIRONMENT --------------------------------------------------------
+# The script depends on various environment variables to exist in order to
+# run properly. The java version we want to use, the location of the java
+# binaries (java home), and the project version as defined inside the pom.xml
+# file, e.g. 1.0-SNAPSHOT.
+#
+# PROJECT_VERSION: version used in pom.xml, e.g. 1.0-SNAPSHOT
+# APP_VERSION: the application version, e.g. 1.0.0, shown in "about" dialog
+
+JAVA_VERSION=23
+MAIN_JAR="app-$PROJECT_VERSION.jar"
+
+# Set desired installer type: "dmg", "pkg", "deb", "app-image".
+INSTALLER_TYPE=app-image
+
+echo "java home: $JAVA_HOME"
+echo "project version: $PROJECT_VERSION"
+echo "app version: $APP_VERSION"
+echo "main JAR file: $MAIN_JAR"
+echo "Java home : $JAVA_HOME"
+
+# ------ SETUP DIRECTORIES AND FILES ----------------------------------------
+# Remove previously generated java runtime and installers. Copy all required
+# jar files into the input/libs folder.
+rm -rfd ./target/java-runtime/
+rm -rfd target/installer/
+
+mkdir -p target/installer/input/libs/
+
+cp target/libs/* target/installer/input/libs/
+cp target/${MAIN_JAR} target/installer/input/libs/
+
+# ------ REQUIRED MODULES ---------------------------------------------------
+# Use jlink to detect all modules that are required to run the application.
+# Starting point for the jdep analysis is the set of jars being used by the
+# application.
+
+echo "detecting required modules"
+$JAVA_HOME/bin/jdeps \
+ -v \
+ --multi-release ${JAVA_VERSION} \
+ --ignore-missing-deps \
+ --class-path "target/installer/input/libs/*" \
+ target/classes/com/johnpickup/app/javafx/MainForm.class
+detected_modules=`$JAVA_HOME/bin/jdeps \
+ -q \
+ --multi-release ${JAVA_VERSION} \
+ --ignore-missing-deps \
+ --print-module-deps \
+ --class-path "target/installer/input/libs/*" \
+ target/classes/com/johnpickup/app/javafx/MainForm.class`
+echo "detected modules: ${detected_modules}"
+
+
+# ------ MANUAL MODULES -----------------------------------------------------
+# jdk.crypto.ec has to be added manually bound via --bind-services or
+# otherwise HTTPS does not work.
+#
+# See: https://bugs.openjdk.java.net/browse/JDK-8221674
+#
+# In addition we need jdk.localedata if the application is localized.
+# This can be reduced to the actually needed locales via a jlink parameter,
+# e.g., --include-locales=en,de.
+#
+# Don't forget the leading ','!
+
+manual_modules=,jdk.crypto.ec,jdk.localedata
+echo "manual modules: ${manual_modules}"
+
+# ------ RUNTIME IMAGE ------------------------------------------------------
+# Use the jlink tool to create a runtime image for our application. We are
+# doing this in a separate step instead of letting jlink do the work as part
+# of the jpackage tool. This approach allows for finer configuration and also
+# works with dependencies that are not fully modularized, yet.
+echo "creating java runtime image"
+$JAVA_HOME/bin/jlink \
+ --strip-native-commands \
+ --no-header-files \
+ --bind-services \
+ --no-man-pages \
+ --strip-debug \
+ --add-modules "${detected_modules}${manual_modules}" \
+ --include-locales=en,de \
+ --output target/java-runtime
+
+# ------ PACKAGING ----------------------------------------------------------
+# In the end we will find the package inside the target/installer directory.
+echo "Creating installer of type $INSTALLER_TYPE"
+$JAVA_HOME/bin/jpackage \
+--type $INSTALLER_TYPE \
+--dest target/installer/ \
+--input target/installer/input/libs \
+--name garmintools \
+--main-class com.johnpickup.app.javafx.AppLauncher \
+--main-jar ${MAIN_JAR} \
+--java-options -Xmx2048m \
+--java-options '--sun-misc-unsafe-memory-access=allow' \
+--runtime-image target/java-runtime \
+--app-version ${APP_VERSION} \
+--vendor "John Pickup" \
+--copyright "Copyright © 2025 John Pickup" \
+
+cd target || exit
+curl -L https://github.com/AppImage/AppImageKit/releases/download/10/appimagetool-x86_64.AppImage -o appimagetool.AppImage
+chmod +x appimagetool.AppImage
+./appimagetool.AppImage --appimage-extract
+export PATH=./squashfs-root/usr/bin/:${PATH}
+
+APP_DIR=GarminTools.AppDir/
+mkdir -p ${APP_DIR}
+cp ../src/main/resources/GarminTools.desktop ${APP_DIR}
+cp ../src/main/resources/garmintools.png ${APP_DIR}
+cp -r installer/garmintools/* ${APP_DIR}
+cd ${APP_DIR} || exit
+ln -s bin/garmintools AppRun
+cd .. || exit
+appimagetool ${APP_DIR} garmintools-x86_64.AppImage
+cp garmintools-x86_64.AppImage ../installer/
+cp garmintools-x86_64.AppImage ../../installer/
\ No newline at end of file
diff --git a/app/build_app_mac.sh b/app/build_app_mac.sh
index 1c55090..f793c8d 100755
--- a/app/build_app_mac.sh
+++ b/app/build_app_mac.sh
@@ -106,4 +106,7 @@ $JAVA_HOME/bin/jpackage \
--vendor "John Pickup" \
--copyright "Copyright © 2023 John Pickup" \
--mac-package-identifier com.johnpickup.garmintools.app \
---mac-package-name ACME
\ No newline at end of file
+--mac-package-name GarminTools
+
+cp target/installer/GarminTools*.pkg ../installer/
+
diff --git a/app/build_app_windows.bat b/app/build_app_windows.bat
index 8889e09..e777800 100644
--- a/app/build_app_windows.bat
+++ b/app/build_app_windows.bat
@@ -97,3 +97,11 @@ rem --win-dir-chooser ^
rem --win-shortcut ^
rem --win-per-user-install ^
rem --win-menu
+echo Current dir
+cd
+echo Listing output
+dir target\installer\
+echo Copying installer output
+xcopy /S /Y /F target\installer\* ..\installer\
+echo Listing of copied files
+dir ..\installer\
\ No newline at end of file
diff --git a/app/pom.xml b/app/pom.xml
index 8c15e9f..cc82b04 100644
--- a/app/pom.xml
+++ b/app/pom.xml
@@ -6,14 +6,16 @@
com.johnpickup.garmin
GarminTools
- 1.0-SNAPSHOT
+ 1.1
app
+ true
1.0.0
1.6.0
+ 3.7.1
24.0.1
2.18.2
2.14.0
@@ -217,15 +219,51 @@
-
- build-mac
+ unix
+
+
+ unix
+
+
+
+
+
+ exec-maven-plugin
+ org.codehaus.mojo
+ ${exec.maven.plugin.version}
+
+
+ Build Native Linux App
+ install
+
+ exec
+
+
+
+
+ ${project.basedir}
+ ./build_app_linux.sh
+
+
+ ${client.version}
+
+
+ ${project.version}
+
+
+
+
+
+
+
+
+ build-mac
mac
-
@@ -260,11 +298,9 @@
build-windows
-
windows
-
@@ -296,13 +332,5 @@
-
-
-
-
- local-repo
- file://${basedir}/../local-repo
-
-
\ No newline at end of file
diff --git a/app/src/main/java/com/johnpickup/app/GarminScheduleGenerator.java b/app/src/main/java/com/johnpickup/app/GarminScheduleGenerator.java
index b2cc1d5..8ffb608 100755
--- a/app/src/main/java/com/johnpickup/app/GarminScheduleGenerator.java
+++ b/app/src/main/java/com/johnpickup/app/GarminScheduleGenerator.java
@@ -52,7 +52,7 @@ public void generate(File inputFile, File outputDir) throws IOException {
workoutSaver.save(converter.getTrainingSchedule(), scheduleFile);
log.info("Saved workout schedule as {}", scheduleFile.getPath());
}
- catch (RuntimeException e) {
+ catch (Exception e) {
log.error("Error converting {}", inputFile.getPath());
log.error(e.getMessage());
}
diff --git a/app/src/main/java/com/johnpickup/app/converter/CustomPowerConverter.java b/app/src/main/java/com/johnpickup/app/converter/CustomPowerConverter.java
new file mode 100755
index 0000000..81faeb1
--- /dev/null
+++ b/app/src/main/java/com/johnpickup/app/converter/CustomPowerConverter.java
@@ -0,0 +1,24 @@
+package com.johnpickup.app.converter;
+
+import com.johnpickup.garmin.common.unit.CustomPowerTarget;
+import com.johnpickup.garmin.common.unit.PowerTarget;
+import com.johnpickup.garmin.common.unit.PowerUnit;
+import com.johnpickup.garmin.parser.Power;
+import com.johnpickup.garmin.parser.PowerRange;
+
+public class CustomPowerConverter implements PowerConverter {
+ @Override
+ public PowerTarget convert(Power power) {
+ PowerRange powerRange = (PowerRange) power;
+ PowerUnit unit;
+ switch (powerRange.getUnit()) {
+ case WATTS:
+ unit = PowerUnit.WATTS;
+ break;
+ default:
+ throw new RuntimeException("Unknown power unit: " + powerRange.getUnit());
+ }
+
+ return new CustomPowerTarget(powerRange.getMinimum(), powerRange. getMaximum(), unit);
+ }
+}
diff --git a/app/src/main/java/com/johnpickup/app/converter/DistancePowerStepConverter.java b/app/src/main/java/com/johnpickup/app/converter/DistancePowerStepConverter.java
new file mode 100755
index 0000000..cc4783c
--- /dev/null
+++ b/app/src/main/java/com/johnpickup/app/converter/DistancePowerStepConverter.java
@@ -0,0 +1,28 @@
+package com.johnpickup.app.converter;
+
+import com.johnpickup.app.garmin.workout.DistancePowerWorkoutStep;
+import com.johnpickup.app.garmin.workout.WorkoutStep;
+import com.johnpickup.garmin.common.unit.Distance;
+import com.johnpickup.garmin.common.unit.PowerTarget;
+import com.johnpickup.garmin.parser.DistancePowerStep;
+import com.johnpickup.garmin.parser.Step;
+
+/**
+ * Convert independent pace steps into the Garmin equivalent
+ */
+public class DistancePowerStepConverter implements StepConverter {
+ @Override
+ public WorkoutStep convert(Step step) {
+ DistancePowerStep distancePowerStep = (DistancePowerStep)step;
+
+ Distance d = new Distance(
+ distancePowerStep.getDistance().getQuantity(),
+ DiatanceUnitConverter.convert(distancePowerStep.getDistance().getUnit()));
+
+ PowerTarget powerTarget = PowerConverterFactory.getInstance()
+ .getPowerConverter(distancePowerStep.getPower())
+ .convert(distancePowerStep.getPower());
+
+ return new DistancePowerWorkoutStep(StepIntensityConverter.convert(step.getStepIntensity()), d, powerTarget);
+ }
+}
diff --git a/app/src/main/java/com/johnpickup/app/converter/OpenPowerStepConverter.java b/app/src/main/java/com/johnpickup/app/converter/OpenPowerStepConverter.java
new file mode 100755
index 0000000..0dc6056
--- /dev/null
+++ b/app/src/main/java/com/johnpickup/app/converter/OpenPowerStepConverter.java
@@ -0,0 +1,23 @@
+package com.johnpickup.app.converter;
+
+import com.johnpickup.app.garmin.workout.OpenPowerWorkoutStep;
+import com.johnpickup.app.garmin.workout.WorkoutStep;
+import com.johnpickup.garmin.common.unit.PowerTarget;
+import com.johnpickup.garmin.parser.OpenPowerStep;
+import com.johnpickup.garmin.parser.Step;
+
+/**
+ * Convert independent pace steps into the Garmin equivalent
+ */
+public class OpenPowerStepConverter implements StepConverter {
+ @Override
+ public WorkoutStep convert(Step step) {
+ OpenPowerStep openPowerStep = (OpenPowerStep)step;
+
+ PowerTarget powerTarget = PowerConverterFactory.getInstance()
+ .getPowerConverter(openPowerStep.getPower())
+ .convert(openPowerStep.getPower());
+
+ return new OpenPowerWorkoutStep(StepIntensityConverter.convert(step.getStepIntensity()), powerTarget);
+ }
+}
diff --git a/app/src/main/java/com/johnpickup/app/converter/PowerConverter.java b/app/src/main/java/com/johnpickup/app/converter/PowerConverter.java
new file mode 100755
index 0000000..4c27e00
--- /dev/null
+++ b/app/src/main/java/com/johnpickup/app/converter/PowerConverter.java
@@ -0,0 +1,13 @@
+package com.johnpickup.app.converter;
+
+import com.johnpickup.garmin.common.unit.PowerTarget;
+import com.johnpickup.garmin.parser.Power;
+
+/**
+ * Interface that pace converters must implement.
+ * One converter will be implemented for each sub-type of Pace and will emit a corresponding
+ * instance of a Garmin PaceTarget
+ */
+public interface PowerConverter {
+ PowerTarget convert(Power power);
+}
diff --git a/app/src/main/java/com/johnpickup/app/converter/PowerConverterFactory.java b/app/src/main/java/com/johnpickup/app/converter/PowerConverterFactory.java
new file mode 100755
index 0000000..0b1cae0
--- /dev/null
+++ b/app/src/main/java/com/johnpickup/app/converter/PowerConverterFactory.java
@@ -0,0 +1,36 @@
+package com.johnpickup.app.converter;
+
+import com.johnpickup.garmin.parser.Power;
+import com.johnpickup.garmin.parser.PowerRange;
+import com.johnpickup.garmin.parser.PowerZone;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Factory that returns the correct converter instance based on the type of heart rate target object passed in
+ */
+public class PowerConverterFactory {
+ private static PowerConverterFactory instance;
+ private final Map converters = new HashMap<>();
+
+ private PowerConverterFactory() {
+ register(new ZonePowerConverter(), PowerZone.class);
+ register(new CustomPowerConverter(), PowerRange.class);
+ }
+
+ public void register(PowerConverter converter, Class aClass) {
+ converters.put(aClass, converter);
+ }
+
+ public static PowerConverterFactory getInstance() {
+ if (instance == null) {
+ instance = new PowerConverterFactory();
+ }
+ return instance;
+ }
+
+ public PowerConverter getPowerConverter(Power power) {
+ return converters.get(power.getClass());
+ }
+}
diff --git a/app/src/main/java/com/johnpickup/app/converter/StepConverterFactory.java b/app/src/main/java/com/johnpickup/app/converter/StepConverterFactory.java
index 41280bb..a35d9e6 100755
--- a/app/src/main/java/com/johnpickup/app/converter/StepConverterFactory.java
+++ b/app/src/main/java/com/johnpickup/app/converter/StepConverterFactory.java
@@ -10,18 +10,21 @@
*/
public class StepConverterFactory {
private static StepConverterFactory instance;
- private Map converters = new HashMap<>();
+ private final Map converters = new HashMap<>();
private StepConverterFactory() {
register(new DistanceStepConverter(), DistanceStep.class);
register(new DistancePaceStepConverter(), DistancePaceStep.class);
register(new DistanceHeartRateStepConverter(), DistanceHeartRateStep.class);
+ register(new DistancePowerStepConverter(), DistancePowerStep.class);
register(new TimeStepConverter(), TimeStep.class);
register(new TimePaceStepConverter(), TimePaceStep.class);
register(new TimeHeartRateStepConverter(), TimeHeartRateStep.class);
+ register(new TimePowerStepConverter(), TimePowerStep.class);
register(new OpenStepConverter(), OpenStep.class);
register(new OpenPaceStepConverter(), OpenPaceStep.class);
register(new OpenHeartRateStepConverter(), OpenHeartRateStep.class);
+ register(new OpenPowerStepConverter(), OpenPowerStep.class);
register(new RepeatingStepsConverter(), RepeatingSteps.class);
}
diff --git a/app/src/main/java/com/johnpickup/app/converter/TimePowerStepConverter.java b/app/src/main/java/com/johnpickup/app/converter/TimePowerStepConverter.java
new file mode 100755
index 0000000..63fb141
--- /dev/null
+++ b/app/src/main/java/com/johnpickup/app/converter/TimePowerStepConverter.java
@@ -0,0 +1,26 @@
+package com.johnpickup.app.converter;
+
+import com.johnpickup.app.garmin.workout.TimePowerWorkoutStep;
+import com.johnpickup.app.garmin.workout.WorkoutStep;
+import com.johnpickup.garmin.common.unit.PowerTarget;
+import com.johnpickup.garmin.common.unit.Time;
+import com.johnpickup.garmin.parser.Step;
+import com.johnpickup.garmin.parser.TimePowerStep;
+
+/**
+ * Convert independent pace steps into the Garmin equivalent
+ */
+public class TimePowerStepConverter implements StepConverter {
+ @Override
+ public WorkoutStep convert(Step step) {
+ TimePowerStep timePowerStep = (TimePowerStep)step;
+
+ Time t = new Time(timePowerStep.getTime().asDouble() * 60);
+
+ PowerTarget powerTarget = PowerConverterFactory.getInstance()
+ .getPowerConverter(timePowerStep.getPower())
+ .convert(timePowerStep.getPower());
+
+ return new TimePowerWorkoutStep(StepIntensityConverter.convert(step.getStepIntensity()), t, powerTarget);
+ }
+}
diff --git a/app/src/main/java/com/johnpickup/app/converter/WorkoutConverter.java b/app/src/main/java/com/johnpickup/app/converter/WorkoutConverter.java
index f3937f5..6ff7914 100755
--- a/app/src/main/java/com/johnpickup/app/converter/WorkoutConverter.java
+++ b/app/src/main/java/com/johnpickup/app/converter/WorkoutConverter.java
@@ -27,6 +27,9 @@ public class WorkoutConverter {
sportMap.put(com.johnpickup.garmin.parser.Sport.SWIMMING, Sport.SWIMMING);
sportMap.put(com.johnpickup.garmin.parser.Sport.POOL_SWIMMING, Sport.SWIMMING);
sportMap.put(com.johnpickup.garmin.parser.Sport.OPEN_WATER_SWIMMING, Sport.SWIMMING);
+ sportMap.put(com.johnpickup.garmin.parser.Sport.CARDIO, Sport.TRAINING);
+ sportMap.put(com.johnpickup.garmin.parser.Sport.STRENGTH, Sport.TRAINING);
+ sportMap.put(com.johnpickup.garmin.parser.Sport.HIIT, Sport.HIIT);
}
private static final Map subSportMap = new HashMap<>();
@@ -38,6 +41,9 @@ public class WorkoutConverter {
subSportMap.put(com.johnpickup.garmin.parser.Sport.SWIMMING, SubSport.LAP_SWIMMING);
subSportMap.put(com.johnpickup.garmin.parser.Sport.POOL_SWIMMING, SubSport.LAP_SWIMMING);
subSportMap.put(com.johnpickup.garmin.parser.Sport.OPEN_WATER_SWIMMING, SubSport.OPEN_WATER);
+ subSportMap.put(com.johnpickup.garmin.parser.Sport.CARDIO, SubSport.CARDIO_TRAINING);
+ subSportMap.put(com.johnpickup.garmin.parser.Sport.STRENGTH, SubSport.STRENGTH_TRAINING);
+ subSportMap.put(com.johnpickup.garmin.parser.Sport.HIIT, SubSport.HIIT);
}
public com.johnpickup.app.garmin.workout.Workout convert(Workout workout) {
diff --git a/app/src/main/java/com/johnpickup/app/converter/WorkoutScheduleConverter.java b/app/src/main/java/com/johnpickup/app/converter/WorkoutScheduleConverter.java
index 8cad0f9..a564b65 100755
--- a/app/src/main/java/com/johnpickup/app/converter/WorkoutScheduleConverter.java
+++ b/app/src/main/java/com/johnpickup/app/converter/WorkoutScheduleConverter.java
@@ -13,6 +13,7 @@
* Created by john on 12/01/2017.
*/
public class WorkoutScheduleConverter {
+ private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(WorkoutScheduleConverter.class);
private final Map namedPaces = new HashMap<>();
private final List garminWorkouts = new ArrayList<>();
private final TrainingSchedule trainingSchedule = new TrainingSchedule();
@@ -25,27 +26,37 @@ public WorkoutScheduleConverter() {
public void convert(WorkoutSchedule workoutSchedule) {
init();
+ log.debug("Converting paces");
for (Map.Entry namedPace : workoutSchedule.getPaces().entrySet()) {
Pace pace = namedPace.getValue();
+ log.debug("Pace: {}", pace);
namedPaces.put(namedPace.getKey(), PaceConverterFactory.getInstance().getPaceConverter(pace).convert(pace));
}
+ log.debug("Done converting paces");
WorkoutConverter workoutConverter = new WorkoutConverter();
+ log.debug("Converting workouts");
for (Map.Entry workoutEntry : workoutSchedule.getWorkouts().entrySet()) {
+ log.debug("Converting {} ", workoutEntry);
com.johnpickup.app.garmin.workout.Workout garminWorkout = workoutConverter.convert(workoutEntry.getValue());
garminWorkout.setName(workoutEntry.getKey());
+ log.debug("Workout: {}", garminWorkout);
garminWorkouts.add(garminWorkout);
workoutMap.put(workoutEntry.getValue(), garminWorkout);
}
+ log.debug("Done converting workouts");
+ log.debug("Converting schedules");
for (ScheduledWorkout scheduledWorkout: workoutSchedule.getSchedule()) {
Workout workout = scheduledWorkout.getWorkout();
com.johnpickup.app.garmin.workout.Workout garminWorkout = workoutMap.get(workout);
com.johnpickup.app.garmin.schedule.ScheduledWorkout garminScheduledWorkout =
new com.johnpickup.app.garmin.schedule.ScheduledWorkout(garminWorkout, scheduledWorkout.getDate());
+ log.debug("Workout schedule: {}", garminScheduledWorkout);
trainingSchedule.addScheduledWorkout(garminScheduledWorkout);
}
+ log.debug("Done converting schedules");
}
diff --git a/app/src/main/java/com/johnpickup/app/converter/ZonePowerConverter.java b/app/src/main/java/com/johnpickup/app/converter/ZonePowerConverter.java
new file mode 100755
index 0000000..df245e3
--- /dev/null
+++ b/app/src/main/java/com/johnpickup/app/converter/ZonePowerConverter.java
@@ -0,0 +1,14 @@
+package com.johnpickup.app.converter;
+
+import com.johnpickup.garmin.common.unit.PowerTarget;
+import com.johnpickup.garmin.common.unit.ZonePowerTarget;
+import com.johnpickup.garmin.parser.Power;
+import com.johnpickup.garmin.parser.PowerZone;
+
+public class ZonePowerConverter implements PowerConverter {
+ @Override
+ public PowerTarget convert(Power power) {
+ PowerZone powerZone = (PowerZone) power;
+ return new ZonePowerTarget(powerZone.getZoneNumber());
+ }
+}
diff --git a/app/src/main/java/com/johnpickup/app/excel/ExcelUtils.java b/app/src/main/java/com/johnpickup/app/excel/ExcelUtils.java
index b6d2d3f..f8658c3 100644
--- a/app/src/main/java/com/johnpickup/app/excel/ExcelUtils.java
+++ b/app/src/main/java/com/johnpickup/app/excel/ExcelUtils.java
@@ -29,6 +29,9 @@ public static Sport readSportValue(Row row, Integer index) {
case "SWIMMING", "SWIM" -> Sport.SWIMMING;
case "POOL SWIMMING", "POOL" -> Sport.POOL_SWIMMING;
case "OPEN WATER SWIMMING", "OPEN WATER" -> Sport.OPEN_WATER_SWIMMING;
+ case "CARDIO" -> Sport.CARDIO;
+ case "STRENGTH" -> Sport.STRENGTH;
+ case "HIIT" -> Sport.HIIT;
default -> Sport.RUNNING;
};
}
diff --git a/app/src/main/java/com/johnpickup/app/excel/ScheduleSheetReader.java b/app/src/main/java/com/johnpickup/app/excel/ScheduleSheetReader.java
index d360ed9..0f204db 100755
--- a/app/src/main/java/com/johnpickup/app/excel/ScheduleSheetReader.java
+++ b/app/src/main/java/com/johnpickup/app/excel/ScheduleSheetReader.java
@@ -44,7 +44,7 @@ public List readSchedule(Sheet sheet, Map wor
private ScheduledWorkout readScheduledWorkout(Row row, Map workouts) {
Cell dateCell = row.getCell(dateIndex);
Cell workoutCell = row.getCell(workoutIndex);
- if (dateCell != null && workoutCell != null) {
+ if (dateCell != null && workoutCell != null && dateCell.getDateCellValue() != null) {
Date date = dateCell.getDateCellValue();
LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
String value = workoutCell.getStringCellValue();
diff --git a/app/src/main/java/com/johnpickup/app/garmin/schedule/ScheduledWorkout.java b/app/src/main/java/com/johnpickup/app/garmin/schedule/ScheduledWorkout.java
index b524fb0..41834f4 100755
--- a/app/src/main/java/com/johnpickup/app/garmin/schedule/ScheduledWorkout.java
+++ b/app/src/main/java/com/johnpickup/app/garmin/schedule/ScheduledWorkout.java
@@ -35,4 +35,12 @@ public Workout getWorkout() {
public LocalDate getDate() {
return this.date;
}
+
+ @Override
+ public String toString() {
+ return "ScheduledWorkout{" +
+ "workout=" + workout +
+ ", date=" + date +
+ '}';
+ }
}
diff --git a/app/src/main/java/com/johnpickup/app/garmin/workout/DistancePowerWorkoutStep.java b/app/src/main/java/com/johnpickup/app/garmin/workout/DistancePowerWorkoutStep.java
new file mode 100755
index 0000000..d771a3c
--- /dev/null
+++ b/app/src/main/java/com/johnpickup/app/garmin/workout/DistancePowerWorkoutStep.java
@@ -0,0 +1,65 @@
+package com.johnpickup.app.garmin.workout;
+
+import com.garmin.fit.Intensity;
+import com.garmin.fit.WktStepDuration;
+import com.garmin.fit.WktStepTarget;
+import com.garmin.fit.WorkoutStepMesg;
+import com.johnpickup.garmin.common.unit.Distance;
+import com.johnpickup.garmin.common.unit.PowerTarget;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Simple workout that lasts a specific distance with a heart rate target
+ */
+public class DistancePowerWorkoutStep extends WorkoutStep {
+ private final Distance distance;
+ private final PowerTarget powerTarget;
+
+ public DistancePowerWorkoutStep(Intensity intensity, Distance distance, PowerTarget powerTarget) {
+ super(intensity);
+ this.distance = distance;
+ this.powerTarget = powerTarget;
+ }
+
+ @Override
+ public String getName() {
+ return distance.toString() + " " + powerTarget.toString();
+ }
+
+ @Override
+ public List generateWorkoutSteps() {
+ WorkoutStepMesg step = new WorkoutStepMesg();
+ step.setIntensity(intensity);
+ step.setDurationType(WktStepDuration.DISTANCE);
+ step.setDurationDistance(distance.toGarminDistance());
+ step.setTargetType(WktStepTarget.POWER);
+ step.setTargetValue(powerTarget.getTargetValue());
+ step.setMessageIndex(generateWorkoutStepIndex());
+ step.setCustomTargetValueLow(powerTarget.getGarminLow());
+ step.setCustomTargetValueHigh(powerTarget.getGarminHigh());
+ step.setNotes(nameWithIntensity());
+
+ return Collections.singletonList(step);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ DistancePowerWorkoutStep that = (DistancePowerWorkoutStep) o;
+ return Objects.equals(distance, that.distance) && Objects.equals(powerTarget, that.powerTarget);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(distance, powerTarget);
+ }
+
+ protected boolean canEqual(final Object other) {
+ return other instanceof DistancePowerWorkoutStep;
+ }
+
+}
diff --git a/app/src/main/java/com/johnpickup/app/garmin/workout/OpenPowerWorkoutStep.java b/app/src/main/java/com/johnpickup/app/garmin/workout/OpenPowerWorkoutStep.java
new file mode 100755
index 0000000..2d477af
--- /dev/null
+++ b/app/src/main/java/com/johnpickup/app/garmin/workout/OpenPowerWorkoutStep.java
@@ -0,0 +1,61 @@
+package com.johnpickup.app.garmin.workout;
+
+import com.garmin.fit.Intensity;
+import com.garmin.fit.WktStepDuration;
+import com.garmin.fit.WktStepTarget;
+import com.garmin.fit.WorkoutStepMesg;
+import com.johnpickup.garmin.common.unit.PowerTarget;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Simple workout step that's open with a heart rate target
+ */
+public class OpenPowerWorkoutStep extends WorkoutStep {
+ private final PowerTarget powerTarget;
+
+ public OpenPowerWorkoutStep(Intensity intensity, PowerTarget powerTarget) {
+ super(intensity);
+ this.powerTarget = powerTarget;
+ }
+
+ @Override
+ public String getName() {
+ return "Open " + powerTarget.toString();
+ }
+
+ @Override
+ public List generateWorkoutSteps() {
+ WorkoutStepMesg step = new WorkoutStepMesg();
+ step.setIntensity(intensity);
+ step.setDurationType(WktStepDuration.OPEN);
+ step.setTargetType(WktStepTarget.POWER);
+ step.setTargetValue(powerTarget.getTargetValue());
+ step.setMessageIndex(generateWorkoutStepIndex());
+ step.setCustomTargetValueLow(powerTarget.getGarminLow());
+ step.setCustomTargetValueHigh(powerTarget.getGarminHigh());
+ step.setNotes(nameWithIntensity());
+
+ return Collections.singletonList(step);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ OpenPowerWorkoutStep that = (OpenPowerWorkoutStep) o;
+ return Objects.equals(powerTarget, that.powerTarget);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(powerTarget);
+ }
+
+ protected boolean canEqual(final Object other) {
+ return other instanceof OpenPowerWorkoutStep;
+ }
+
+}
diff --git a/app/src/main/java/com/johnpickup/app/garmin/workout/TimePowerWorkoutStep.java b/app/src/main/java/com/johnpickup/app/garmin/workout/TimePowerWorkoutStep.java
new file mode 100755
index 0000000..b4ef161
--- /dev/null
+++ b/app/src/main/java/com/johnpickup/app/garmin/workout/TimePowerWorkoutStep.java
@@ -0,0 +1,64 @@
+package com.johnpickup.app.garmin.workout;
+
+import com.garmin.fit.Intensity;
+import com.garmin.fit.WktStepDuration;
+import com.garmin.fit.WktStepTarget;
+import com.garmin.fit.WorkoutStepMesg;
+import com.johnpickup.garmin.common.unit.PowerTarget;
+import com.johnpickup.garmin.common.unit.Time;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Simple workout that lasts a specific distance with a pace target
+ */
+public class TimePowerWorkoutStep extends WorkoutStep {
+ private final Time time;
+ private final PowerTarget powerTarget;
+
+ public TimePowerWorkoutStep(Intensity intensity, Time time, PowerTarget powerTarget) {
+ super(intensity);
+ this.time = time;
+ this.powerTarget = powerTarget;
+ }
+
+ @Override
+ public String getName() {
+ return time.toString() + " " + powerTarget.toString();
+ }
+
+ @Override
+ public List generateWorkoutSteps() {
+ WorkoutStepMesg step = new WorkoutStepMesg();
+ step.setIntensity(intensity);
+ step.setDurationType(WktStepDuration.TIME);
+ step.setDurationDistance(time.toGarminTime());
+ step.setTargetType(WktStepTarget.POWER);
+ step.setTargetValue(powerTarget.getTargetValue());
+ step.setMessageIndex(generateWorkoutStepIndex());
+ step.setCustomTargetValueLow(powerTarget.getGarminLow());
+ step.setCustomTargetValueHigh(powerTarget.getGarminHigh());
+ step.setNotes(nameWithIntensity());
+ return Collections.singletonList(step);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ TimePowerWorkoutStep that = (TimePowerWorkoutStep) o;
+ return Objects.equals(time, that.time) && Objects.equals(powerTarget, that.powerTarget);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(time, powerTarget);
+ }
+
+ protected boolean canEqual(final Object other) {
+ return other instanceof TimePowerWorkoutStep;
+ }
+
+}
diff --git a/app/src/main/java/com/johnpickup/app/javafx/AppLauncher.java b/app/src/main/java/com/johnpickup/app/javafx/AppLauncher.java
index 4314a2c..2d1e0ab 100644
--- a/app/src/main/java/com/johnpickup/app/javafx/AppLauncher.java
+++ b/app/src/main/java/com/johnpickup/app/javafx/AppLauncher.java
@@ -1,7 +1,13 @@
package com.johnpickup.app.javafx;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
public class AppLauncher {
+ private static final Logger log = LoggerFactory.getLogger(AppLauncher.class);
+
public static void main(String[] args) {
+ Thread.setDefaultUncaughtExceptionHandler((t, e) -> log.error("Unhandled error", e));
MainForm.main(args);
}
}
diff --git a/app/src/main/resources/GarminTools.desktop b/app/src/main/resources/GarminTools.desktop
new file mode 100644
index 0000000..9814ac5
--- /dev/null
+++ b/app/src/main/resources/GarminTools.desktop
@@ -0,0 +1,8 @@
+[Desktop Entry]
+Type=Application
+Name=garmintools
+Exec=bin/garmintools
+Comment=A suite of tools for Garmin watches
+Icon=garmintools
+Terminal=false
+Categories=Utility;
diff --git a/app/src/main/resources/control b/app/src/main/resources/control
new file mode 100644
index 0000000..3d02b04
--- /dev/null
+++ b/app/src/main/resources/control
@@ -0,0 +1,8 @@
+Package: garmintools
+Architecture: all
+Version: 1.0.0
+Section: misc
+Maintainer: John Pickup
+Priority: optional
+Standards-Version: 4.7.0
+Description: Garmin Tools
diff --git a/app/src/main/resources/garmintools.png b/app/src/main/resources/garmintools.png
new file mode 100755
index 0000000..e598efc
Binary files /dev/null and b/app/src/main/resources/garmintools.png differ
diff --git a/bundle/pom.xml b/bundle/pom.xml
new file mode 100644
index 0000000..87e6dda
--- /dev/null
+++ b/bundle/pom.xml
@@ -0,0 +1,126 @@
+
+
+ 4.0.0
+
+ com.johnpickup.garmin
+ GarminTools
+ 1.1
+
+
+ bundle
+
+
+ false
+ 1.6.0
+ 3.7.1
+ 1.0.0
+
+
+
+
+ ${project.groupId}
+ app
+ ${project.version}
+
+
+
+
+
+ unix
+
+
+ unix
+
+
+
+
+
+ maven-assembly-plugin
+ ${maven.assembly.plugin.version}
+
+
+ src/assembly/linux.xml
+
+
+
+
+ make-linux-assembly
+ install
+
+ single
+
+
+
+
+
+
+
+
+
+ build-mac
+
+ mac
+
+
+
+
+ maven-assembly-plugin
+ ${maven.assembly.plugin.version}
+
+
+ src/assembly/macos.xml
+
+
+
+
+ make-macos-assembly
+ install
+
+ single
+
+
+
+
+
+
+
+
+
+ build-windows
+
+ windows
+
+
+
+
+ maven-assembly-plugin
+ ${maven.assembly.plugin.version}
+
+
+ src/assembly/windows.xml
+
+
+
+
+ make-windows-assembly
+ install
+
+ single
+
+
+
+
+
+
+
+
+
+
+
+ local-repo
+ file://${basedir}/../local-repo
+
+
+
\ No newline at end of file
diff --git a/bundle/src/assembly/linux.xml b/bundle/src/assembly/linux.xml
new file mode 100644
index 0000000..3bcf5ca
--- /dev/null
+++ b/bundle/src/assembly/linux.xml
@@ -0,0 +1,16 @@
+
+ linux
+
+ zip
+
+
+
+ ${project.basedir}/../installer
+ /
+
+ *.AppImage
+
+
+
+
\ No newline at end of file
diff --git a/bundle/src/assembly/macos.xml b/bundle/src/assembly/macos.xml
new file mode 100644
index 0000000..c286355
--- /dev/null
+++ b/bundle/src/assembly/macos.xml
@@ -0,0 +1,16 @@
+
+ macos
+
+ zip
+
+
+
+ ${project.basedir}/../installer
+ /
+
+ *.pkg
+
+
+
+
\ No newline at end of file
diff --git a/bundle/src/assembly/windows.xml b/bundle/src/assembly/windows.xml
new file mode 100644
index 0000000..85c5a12
--- /dev/null
+++ b/bundle/src/assembly/windows.xml
@@ -0,0 +1,16 @@
+
+ windows
+
+ zip
+
+
+
+ ${project.basedir}/../installer/GarminTools
+ /
+
+ **/*
+
+
+
+
\ No newline at end of file
diff --git a/common/pom.xml b/common/pom.xml
index d39d608..065c9f0 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -6,7 +6,7 @@
com.johnpickup.garmin
GarminTools
- 1.0-SNAPSHOT
+ 1.1
common
@@ -15,6 +15,7 @@
${java.version}
${java.version}
UTF-8
+ true
diff --git a/common/src/main/java/com/johnpickup/garmin/common/unit/CustomPowerTarget.java b/common/src/main/java/com/johnpickup/garmin/common/unit/CustomPowerTarget.java
new file mode 100755
index 0000000..aa8b8aa
--- /dev/null
+++ b/common/src/main/java/com/johnpickup/garmin/common/unit/CustomPowerTarget.java
@@ -0,0 +1,52 @@
+package com.johnpickup.garmin.common.unit;
+
+import java.util.Objects;
+
+/**
+ * Power target - a minimum and maximum power in watts
+ */
+public class CustomPowerTarget extends PowerTarget {
+ private final Power maxPower;
+ private final Power minPower;
+
+ public CustomPowerTarget(long min, long max, PowerUnit unit) {
+ this.minPower = new Power(min, unit);
+ this.maxPower = new Power(max, unit);
+ }
+
+ @Override
+ public String toString() {
+ return minPower.toValueString() + "-" + maxPower;
+ }
+
+ public Long getGarminLow() {
+ if (minPower.toGarminPower() < maxPower.toGarminPower())
+ return minPower.toGarminPower();
+ else
+ return maxPower.toGarminPower();
+ }
+
+ public Long getGarminHigh() {
+ if (minPower.toGarminPower() < maxPower.toGarminPower())
+ return maxPower.toGarminPower();
+ else
+ return minPower.toGarminPower();
+ }
+
+ @Override
+ public Long getTargetValue() {
+ return 0L;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) return false;
+ CustomPowerTarget that = (CustomPowerTarget) o;
+ return Objects.equals(maxPower, that.maxPower) && Objects.equals(minPower, that.minPower);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(maxPower, minPower);
+ }
+}
diff --git a/common/src/main/java/com/johnpickup/garmin/common/unit/Power.java b/common/src/main/java/com/johnpickup/garmin/common/unit/Power.java
new file mode 100755
index 0000000..e4f1ce9
--- /dev/null
+++ b/common/src/main/java/com/johnpickup/garmin/common/unit/Power.java
@@ -0,0 +1,47 @@
+package com.johnpickup.garmin.common.unit;
+
+import java.util.Objects;
+
+/**
+ * Encapsulation of custom power value with human-readable toString plus a conversion to Garmin units
+ */
+public class Power {
+ private final long value;
+ private final PowerUnit unit;
+
+ public Power(long value, PowerUnit unit) {
+ this.value = value;
+ this.unit = unit;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s%s", toValueString(), unit.getShortName());
+ }
+
+ public Long toGarminPower() {
+ // Garmin power units are in watts offset by 1000
+ // 0 – 1000% reserved for functional threshold power (FTP)
+ return switch (unit) {
+ case WATTS -> value + 1000;
+ };
+ }
+
+ public String toValueString() {
+ return switch (unit) {
+ case WATTS -> String.format("%d", value);
+ };
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) return false;
+ Power power = (Power) o;
+ return value == power.value && unit == power.unit;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(value, unit);
+ }
+}
diff --git a/common/src/main/java/com/johnpickup/garmin/common/unit/PowerTarget.java b/common/src/main/java/com/johnpickup/garmin/common/unit/PowerTarget.java
new file mode 100755
index 0000000..2ced0be
--- /dev/null
+++ b/common/src/main/java/com/johnpickup/garmin/common/unit/PowerTarget.java
@@ -0,0 +1,13 @@
+package com.johnpickup.garmin.common.unit;
+
+/**
+ * Power target - either a zone or a custom minimum and maximum power (in subclasses)
+ */
+public abstract class PowerTarget {
+
+ public abstract Long getGarminLow();
+
+ public abstract Long getGarminHigh();
+
+ public abstract Long getTargetValue();
+}
diff --git a/common/src/main/java/com/johnpickup/garmin/common/unit/PowerUnit.java b/common/src/main/java/com/johnpickup/garmin/common/unit/PowerUnit.java
new file mode 100755
index 0000000..8fa6f3f
--- /dev/null
+++ b/common/src/main/java/com/johnpickup/garmin/common/unit/PowerUnit.java
@@ -0,0 +1,28 @@
+package com.johnpickup.garmin.common.unit;
+
+public enum PowerUnit {
+ WATTS("watts","W");
+
+ private final String description;
+
+ final String shortName;
+
+ PowerUnit(String description, String shortName) {
+ this.description = description;
+ this.shortName = shortName;
+ }
+
+ @Override
+ public String toString() {
+ return description;
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+ public String getShortName() {
+ return this.shortName;
+ }
+}
+
diff --git a/common/src/main/java/com/johnpickup/garmin/common/unit/ZonePowerTarget.java b/common/src/main/java/com/johnpickup/garmin/common/unit/ZonePowerTarget.java
new file mode 100755
index 0000000..c11bb05
--- /dev/null
+++ b/common/src/main/java/com/johnpickup/garmin/common/unit/ZonePowerTarget.java
@@ -0,0 +1,29 @@
+package com.johnpickup.garmin.common.unit;
+
+public class ZonePowerTarget extends PowerTarget {
+ private final Long zone;
+
+ public ZonePowerTarget(Long zone) {
+ this.zone = zone;
+ }
+
+ @Override
+ public Long getGarminLow() {
+ return 0L;
+ }
+
+ @Override
+ public Long getGarminHigh() {
+ return 0L;
+ }
+
+ @Override
+ public Long getTargetValue() {
+ return zone;
+ }
+
+ @Override
+ public String toString() {
+ return "PZ" + zone;
+ }
+}
diff --git a/gpx/pom.xml b/gpx/pom.xml
index d48bb01..b90cebf 100644
--- a/gpx/pom.xml
+++ b/gpx/pom.xml
@@ -6,7 +6,7 @@
com.johnpickup.garmin
GarminTools
- 1.0-SNAPSHOT
+ 1.1
gpx
@@ -15,6 +15,7 @@
${java.version}
${java.version}
UTF-8
+ true
diff --git a/installer/GarminTools-1.0.0.msi b/installer/GarminTools-1.0.0.msi
deleted file mode 100644
index 9d502f5..0000000
Binary files a/installer/GarminTools-1.0.0.msi and /dev/null differ
diff --git a/installer/GarminTools-1.0.0.pkg b/installer/GarminTools-1.0.0.pkg
deleted file mode 100644
index dd365ca..0000000
--- a/installer/GarminTools-1.0.0.pkg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:71a6552012b04f0e96f8d0b1bd75e6c589ff9b1412b603d51e89beb15f28de67
-size 101283204
diff --git a/installer/GarminTools.zip b/installer/GarminTools.zip
deleted file mode 100644
index d514d8d..0000000
Binary files a/installer/GarminTools.zip and /dev/null differ
diff --git a/parser/pom.xml b/parser/pom.xml
index fef9692..8654e6d 100644
--- a/parser/pom.xml
+++ b/parser/pom.xml
@@ -6,7 +6,7 @@
com.johnpickup.garmin
GarminTools
- 1.0-SNAPSHOT
+ 1.1
parser
@@ -15,6 +15,7 @@
${java.version}
${java.version}
UTF-8
+ true
diff --git a/parser/src/main/antlr4/Workout.g4 b/parser/src/main/antlr4/Workout.g4
index d99830c..4079186 100755
--- a/parser/src/main/antlr4/Workout.g4
+++ b/parser/src/main/antlr4/Workout.g4
@@ -34,18 +34,24 @@ step returns [Step value]
| distance_pace_intensity_step {$value = $distance_pace_intensity_step.value;}
| distance_hr_step {$value = $distance_hr_step.value;}
| distance_hr_intensity_step {$value = $distance_hr_intensity_step.value;}
+ | distance_power_step {$value = $distance_power_step.value;}
+ | distance_power_intensity_step {$value = $distance_power_intensity_step.value;}
| time_step {$value = $time_step.value;}
| time_intensity_step {$value = $time_intensity_step.value;}
| time_pace_step {$value = $time_pace_step.value;}
| time_pace_intensity_step {$value = $time_pace_intensity_step.value;}
| time_hr_step {$value = $time_hr_step.value;}
| time_hr_intensity_step {$value = $time_hr_intensity_step.value;}
+ | time_power_step {$value = $time_power_step.value;}
+ | time_power_intensity_step {$value = $time_power_intensity_step.value;}
| open_step {$value = $open_step.value;}
| open_intensity_step {$value = $open_intensity_step.value;}
| open_pace_step {$value = $open_pace_step.value;}
| open_pace_intensity_step {$value = $open_pace_intensity_step.value;}
| open_hr_step {$value = $open_hr_step.value;}
| open_hr_intensity_step {$value = $open_hr_intensity_step.value;}
+ | open_power_step {$value = $open_power_step.value;}
+ | open_power_intensity_step {$value = $open_power_intensity_step.value;}
| repeating_steps {$value = $repeating_steps.value;}
;
@@ -79,6 +85,16 @@ distance_hr_intensity_step returns [DistanceHeartRateStep value]
| distance '@' hr_zone PIPE intensity {$value = new DistanceHeartRateStep($intensity.value, $distance.value, $hr_zone.value);}
;
+distance_power_step returns [DistancePowerStep value]
+ : distance '@' power_range {$value = new DistancePowerStep($distance.value, $power_range.value);}
+ | distance '@' power_zone {$value = new DistancePowerStep($distance.value, $power_zone.value);}
+ ;
+
+distance_power_intensity_step returns [DistancePowerStep value]
+ : distance '@' power_range PIPE intensity {$value = new DistancePowerStep($intensity.value, $distance.value, $power_range.value);}
+ | distance '@' power_zone PIPE intensity {$value = new DistancePowerStep($intensity.value, $distance.value, $power_zone.value);}
+ ;
+
time_step returns [TimeStep value]
: time {$value = new TimeStep($time.value);}
;
@@ -109,6 +125,16 @@ time_hr_intensity_step returns [TimeHeartRateStep value]
| time '@' hr_zone PIPE intensity {$value = new TimeHeartRateStep($intensity.value, $time.value, $hr_zone.value);}
;
+time_power_step returns [TimePowerStep value]
+ : time '@' power_range {$value = new TimePowerStep($time.value, $power_range.value);}
+ | time '@' power_zone {$value = new TimePowerStep($time.value, $power_zone.value);}
+ ;
+
+time_power_intensity_step returns [TimePowerStep value]
+ : time '@' power_range PIPE intensity {$value = new TimePowerStep($intensity.value, $time.value, $power_range.value);}
+ | time '@' power_zone PIPE intensity {$value = new TimePowerStep($intensity.value, $time.value, $power_zone.value);}
+ ;
+
open_step returns [OpenStep value]
: open {$value = new OpenStep();}
;
@@ -139,6 +165,16 @@ open_hr_intensity_step returns [OpenHeartRateStep value]
| open '@' hr_zone PIPE intensity {$value = new OpenHeartRateStep($intensity.value, $hr_zone.value);}
;
+open_power_step returns [OpenPowerStep value]
+ : open '@' power_range {$value = new OpenPowerStep($power_range.value);}
+ | open '@' power_zone {$value = new OpenPowerStep($power_zone.value);}
+ ;
+
+open_power_intensity_step returns [OpenPowerStep value]
+ : open '@' power_range PIPE intensity {$value = new OpenPowerStep($intensity.value, $power_range.value);}
+ | open '@' power_zone PIPE intensity {$value = new OpenPowerStep($intensity.value, $power_zone.value);}
+ ;
+
repeating_steps returns [RepeatingSteps value]
: '('
s=stepList {$value = new RepeatingSteps($s.steps);}
@@ -175,17 +211,40 @@ hr_unit returns [HeartRateUnit value]
hr_zone returns [HeartRateZone value]
: 'Z1' {$value = HeartRateZone.Z1;}
+ | 'HZ1' {$value = HeartRateZone.Z1;}
| 'z1' {$value = HeartRateZone.Z1;}
| 'Z2' {$value = HeartRateZone.Z2;}
+ | 'HZ2' {$value = HeartRateZone.Z2;}
| 'z2' {$value = HeartRateZone.Z2;}
| 'Z3' {$value = HeartRateZone.Z3;}
+ | 'HZ3' {$value = HeartRateZone.Z3;}
| 'z3' {$value = HeartRateZone.Z3;}
| 'Z4' {$value = HeartRateZone.Z4;}
+ | 'HZ4' {$value = HeartRateZone.Z4;}
| 'z4' {$value = HeartRateZone.Z4;}
| 'Z5' {$value = HeartRateZone.Z5;}
+ | 'HZ5' {$value = HeartRateZone.Z5;}
| 'z5' {$value = HeartRateZone.Z5;}
;
+power_range returns [Power value]
+ : p1=cardinal '-' p2=cardinal power_unit {$value = new PowerRange($p1.value, $p2.value, $power_unit.value);}
+ ;
+
+power_unit returns [PowerUnit value]
+ : 'W' {$value = PowerUnit.WATTS;}
+ ;
+
+power_zone returns [PowerZone value]
+ : 'PZ1' {$value = PowerZone.PZ1;}
+ | 'PZ2' {$value = PowerZone.PZ2;}
+ | 'PZ3' {$value = PowerZone.PZ3;}
+ | 'PZ4' {$value = PowerZone.PZ4;}
+ | 'PZ5' {$value = PowerZone.PZ5;}
+ | 'PZ6' {$value = PowerZone.PZ6;}
+ | 'PZ7' {$value = PowerZone.PZ7;}
+ ;
+
time returns [Time value]
: DIGIT + COLON DIGIT DIGIT {$value = Time.parseTime($text);}
;
diff --git a/parser/src/main/java/com/johnpickup/garmin/parser/DistancePowerStep.java b/parser/src/main/java/com/johnpickup/garmin/parser/DistancePowerStep.java
new file mode 100755
index 0000000..9d843a9
--- /dev/null
+++ b/parser/src/main/java/com/johnpickup/garmin/parser/DistancePowerStep.java
@@ -0,0 +1,50 @@
+package com.johnpickup.garmin.parser;
+
+import java.util.Objects;
+
+public class DistancePowerStep extends Step {
+ private final Distance distance;
+ private final Power power;
+
+ public DistancePowerStep(Distance distance, Power power) {
+ super();
+ this.distance = distance;
+ this.power = power;
+ }
+
+ public DistancePowerStep(StepIntensity stepIntensity, Distance distance, Power power) {
+ super(stepIntensity);
+ this.distance = distance;
+ this.power = power;
+ }
+ @Override
+ public String toString() {
+ return distance + "@" + power + (stepIntensity==null?"":("|"+stepIntensity));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ DistancePowerStep that = (DistancePowerStep) o;
+ return Objects.equals(distance, that.distance) && Objects.equals(power, that.power)
+ && Objects.equals(stepIntensity, that.stepIntensity);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(distance, power);
+ }
+
+ protected boolean canEqual(final Object other) {
+ return other instanceof DistancePowerStep;
+ }
+
+ public Distance getDistance() {
+ return this.distance;
+ }
+
+ public Power getPower() {
+ return this.power;
+ }
+}
diff --git a/parser/src/main/java/com/johnpickup/garmin/parser/OpenPowerStep.java b/parser/src/main/java/com/johnpickup/garmin/parser/OpenPowerStep.java
new file mode 100755
index 0000000..2e7abfe
--- /dev/null
+++ b/parser/src/main/java/com/johnpickup/garmin/parser/OpenPowerStep.java
@@ -0,0 +1,44 @@
+package com.johnpickup.garmin.parser;
+
+import java.util.Objects;
+
+public class OpenPowerStep extends Step {
+ private final Power power;
+
+ public OpenPowerStep(Power power) {
+ super();
+ this.power = power;
+ }
+
+ public OpenPowerStep(StepIntensity stepIntensity, Power power) {
+ super(stepIntensity);
+ this.power = power;
+ }
+
+ @Override
+ public String toString() {
+ return "Open@" + power + (stepIntensity==null?"":("|"+stepIntensity));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ OpenPowerStep that = (OpenPowerStep) o;
+ return Objects.equals(power, that.power)
+ && Objects.equals(stepIntensity, that.stepIntensity);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(power);
+ }
+
+ protected boolean canEqual(final Object other) {
+ return other instanceof OpenPowerStep;
+ }
+
+ public Power getPower() {
+ return this.power;
+ }
+}
diff --git a/parser/src/main/java/com/johnpickup/garmin/parser/Power.java b/parser/src/main/java/com/johnpickup/garmin/parser/Power.java
new file mode 100755
index 0000000..9cc4091
--- /dev/null
+++ b/parser/src/main/java/com/johnpickup/garmin/parser/Power.java
@@ -0,0 +1,4 @@
+package com.johnpickup.garmin.parser;
+
+public interface Power {
+}
diff --git a/parser/src/main/java/com/johnpickup/garmin/parser/PowerRange.java b/parser/src/main/java/com/johnpickup/garmin/parser/PowerRange.java
new file mode 100755
index 0000000..a0a936c
--- /dev/null
+++ b/parser/src/main/java/com/johnpickup/garmin/parser/PowerRange.java
@@ -0,0 +1,49 @@
+package com.johnpickup.garmin.parser;
+
+import java.util.Objects;
+
+public class PowerRange implements Power {
+ private final int minimum;
+ private final int maximum;
+ private final PowerUnit unit;
+
+ public PowerRange(int minimum, int maximum, PowerUnit unit) {
+ this.minimum = minimum;
+ this.maximum = maximum;
+ this.unit = unit;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s-%s%s", minimum, maximum, unit);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PowerRange that = (PowerRange) o;
+ return minimum == that.minimum && maximum == that.maximum && unit == that.unit;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(minimum, maximum, unit);
+ }
+
+ protected boolean canEqual(final Object other) {
+ return other instanceof PowerRange;
+ }
+
+ public int getMinimum() {
+ return this.minimum;
+ }
+
+ public int getMaximum() {
+ return this.maximum;
+ }
+
+ public PowerUnit getUnit() {
+ return this.unit;
+ }
+}
diff --git a/parser/src/main/java/com/johnpickup/garmin/parser/PowerUnit.java b/parser/src/main/java/com/johnpickup/garmin/parser/PowerUnit.java
new file mode 100755
index 0000000..e60ab0f
--- /dev/null
+++ b/parser/src/main/java/com/johnpickup/garmin/parser/PowerUnit.java
@@ -0,0 +1,13 @@
+package com.johnpickup.garmin.parser;
+
+public enum PowerUnit {
+ WATTS;
+
+ @Override
+ public String toString() {
+ switch (this) {
+ case WATTS: return "W";
+ default: return super.toString();
+ }
+ }
+}
diff --git a/parser/src/main/java/com/johnpickup/garmin/parser/PowerZone.java b/parser/src/main/java/com/johnpickup/garmin/parser/PowerZone.java
new file mode 100755
index 0000000..f528823
--- /dev/null
+++ b/parser/src/main/java/com/johnpickup/garmin/parser/PowerZone.java
@@ -0,0 +1,56 @@
+package com.johnpickup.garmin.parser;
+
+import java.util.Objects;
+
+public class PowerZone implements Power {
+ private final Zone zone;
+ private final long zoneNumber;
+ private PowerZone(Zone zone, long zoneNumber) {
+ this.zone = zone;
+ this.zoneNumber = zoneNumber;
+ }
+
+ public static PowerZone PZ1 = new PowerZone(Zone.PZ1, 1);
+ public static PowerZone PZ2 = new PowerZone(Zone.PZ2, 2);
+ public static PowerZone PZ3 = new PowerZone(Zone.PZ3, 3);
+ public static PowerZone PZ4 = new PowerZone(Zone.PZ4, 4);
+ public static PowerZone PZ5 = new PowerZone(Zone.PZ5, 5);
+ public static PowerZone PZ6 = new PowerZone(Zone.PZ6, 6);
+ public static PowerZone PZ7 = new PowerZone(Zone.PZ7, 7);
+
+ @Override
+ public String toString() {
+ return zone.name();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PowerZone that = (PowerZone) o;
+ return zoneNumber == that.zoneNumber && zone == that.zone;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(zone, zoneNumber);
+ }
+
+ protected boolean canEqual(final Object other) {
+ return other instanceof PowerZone;
+ }
+
+ public long getZoneNumber() {
+ return this.zoneNumber;
+ }
+
+ enum Zone {
+ PZ1,
+ PZ2,
+ PZ3,
+ PZ4,
+ PZ5,
+ PZ6,
+ PZ7
+ }
+}
diff --git a/parser/src/main/java/com/johnpickup/garmin/parser/Sport.java b/parser/src/main/java/com/johnpickup/garmin/parser/Sport.java
index 9d84f00..605bb6a 100644
--- a/parser/src/main/java/com/johnpickup/garmin/parser/Sport.java
+++ b/parser/src/main/java/com/johnpickup/garmin/parser/Sport.java
@@ -9,5 +9,8 @@ public enum Sport {
MTB,
SWIMMING,
POOL_SWIMMING,
- OPEN_WATER_SWIMMING
+ OPEN_WATER_SWIMMING,
+ CARDIO,
+ STRENGTH,
+ HIIT
}
diff --git a/parser/src/main/java/com/johnpickup/garmin/parser/TimePowerStep.java b/parser/src/main/java/com/johnpickup/garmin/parser/TimePowerStep.java
new file mode 100755
index 0000000..37f063c
--- /dev/null
+++ b/parser/src/main/java/com/johnpickup/garmin/parser/TimePowerStep.java
@@ -0,0 +1,51 @@
+package com.johnpickup.garmin.parser;
+
+import java.util.Objects;
+
+public class TimePowerStep extends Step {
+ private final Time time;
+ private final Power power;
+
+ public TimePowerStep(Time time, Power power) {
+ super();
+ this.time = time;
+ this.power = power;
+ }
+
+ public TimePowerStep(StepIntensity stepIntensity, Time time, Power power) {
+ super(stepIntensity);
+ this.time = time;
+ this.power = power;
+ }
+
+ @Override
+ public String toString() {
+ return time + "@" + power + (stepIntensity==null?"":("|"+stepIntensity));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ TimePowerStep that = (TimePowerStep) o;
+ return Objects.equals(time, that.time) && Objects.equals(power, that.power)
+ && Objects.equals(stepIntensity, that.stepIntensity);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(time, power);
+ }
+
+ protected boolean canEqual(final Object other) {
+ return other instanceof TimePowerStep;
+ }
+
+ public Time getTime() {
+ return this.time;
+ }
+
+ public Power getPower() {
+ return this.power;
+ }
+}
diff --git a/parser/src/main/java/com/johnpickup/garmin/parser/Workout.java b/parser/src/main/java/com/johnpickup/garmin/parser/Workout.java
index b494d0b..69fd9d3 100755
--- a/parser/src/main/java/com/johnpickup/garmin/parser/Workout.java
+++ b/parser/src/main/java/com/johnpickup/garmin/parser/Workout.java
@@ -61,5 +61,4 @@ public int hashCode() {
protected boolean canEqual(final Object other) {
return other instanceof Workout;
}
-
}
diff --git a/pom.xml b/pom.xml
index 69cc987..3122645 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.johnpickup.garmin
GarminTools
- 1.0-SNAPSHOT
+ 1.1
pom
GarminTools
@@ -14,6 +14,7 @@
parser
gpx
app
+ bundle
@@ -28,8 +29,9 @@
2.3.1
4.7.1
5.2.2
- 1.4.12
+ 1.5.13
${project.basedir}/lib/fit.jar
+ true
@@ -113,4 +115,12 @@
file://${basedir}/local-repo
+
+
+
+ github
+ GitHub Apache Maven Packages
+ https://maven.pkg.github.com/jpickup/GarminTools
+
+
\ No newline at end of file