diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinitionFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinitionFactory.java index e513c709ce..aed667170d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinitionFactory.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinitionFactory.java @@ -6,6 +6,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefi import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionArray; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionBoolean; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionCoordinate; +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionCoursePoint; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionDayOfWeek; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionExerciseCategory; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionFileType; @@ -52,6 +53,7 @@ public class FieldDefinitionFactory { case COORDINATE -> new FieldDefinitionCoordinate(localNumber, size, baseType, name); case SWIM_STYLE -> new FieldDefinitionSwimStyle(localNumber, size, baseType, name); case LOCATION_SYMBOL -> new FieldDefinitionLocationSymbol(localNumber, size, baseType, name, scale, offset); + case COURSE_POINT -> new FieldDefinitionCoursePoint(localNumber, size, baseType, name, scale, offset); }; } @@ -77,6 +79,7 @@ public class FieldDefinitionFactory { WEATHER_AQI, COORDINATE, SWIM_STYLE, - LOCATION_SYMBOL + LOCATION_SYMBOL, + COURSE_POINT } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java index c15c5f6e92..eb264a7d3d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java @@ -736,7 +736,7 @@ public class GlobalFITMessage { new FieldDefinitionPrimitive(2, BaseType.SINT32, "position_lat", FieldDefinitionFactory.FIELD.COORDINATE), new FieldDefinitionPrimitive(3, BaseType.SINT32, "position_long", FieldDefinitionFactory.FIELD.COORDINATE), new FieldDefinitionPrimitive(4, BaseType.UINT32, "distance", 100, 0), // m - new FieldDefinitionPrimitive(5, BaseType.ENUM, "type"), + new FieldDefinitionPrimitive(5, BaseType.ENUM, "type", FieldDefinitionFactory.FIELD.COURSE_POINT), new FieldDefinitionPrimitive(6, BaseType.STRING, 16, "name"), new FieldDefinitionPrimitive(8, BaseType.ENUM, "favorite"), new FieldDefinitionPrimitive(254, BaseType.UINT16, "message_index") diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/codegen/FitCodeGen.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/codegen/FitCodeGen.java index 6ad44fa934..cb6a252823 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/codegen/FitCodeGen.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/codegen/FitCodeGen.java @@ -29,6 +29,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDef import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionAlarmLabel; +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionCoursePoint; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionExerciseCategory; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionGoalSource; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionGoalType; @@ -365,6 +366,7 @@ public class FitCodeGen { case COORDINATE -> Double.class; case SWIM_STYLE -> FieldDefinitionSwimStyle.SwimStyle.class; case LOCATION_SYMBOL -> FieldDefinitionLocationSymbol.LocationSymbol.class; + case COURSE_POINT -> FieldDefinitionCoursePoint.CoursePoint.class; }; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionCoursePoint.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionCoursePoint.java new file mode 100644 index 0000000000..065e61bdfe --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionCoursePoint.java @@ -0,0 +1,151 @@ +/* Copyright (C) 2025 Thomas Kuehne + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition; +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType; + +public class FieldDefinitionCoursePoint extends FieldDefinition { + + public FieldDefinitionCoursePoint(int localNumber, int size, BaseType baseType, String name, int scale, int offset) { + super(localNumber, size, baseType, name, scale, offset); + } + + @Override + public Object decode(ByteBuffer byteBuffer) { + final Object rawObj = baseType.decode(byteBuffer, scale, offset); + if (rawObj != null) { + final Number raw = (Number) rawObj; + return CoursePoint.fromId(raw.intValue()); + } + return null; + } + + @Override + public void encode(ByteBuffer byteBuffer, Object o) { + if (o instanceof CoursePoint coursePoint) { + baseType.encode(byteBuffer, coursePoint.getId(), scale, offset); + return; + } + baseType.encode(byteBuffer, o, scale, offset); + } + + public enum CoursePoint { + GENERIC(0, "generic"), + SUMMIT(1, "summit"), + VALLEY(2, "valley"), + WATER(3, "water"), + FOOD(4, "food"), + DANGER(5, "danger"), + LEFT(6, "left"), + RIGHT(7, "right"), + STRAIGHT(8, "straight"), + FIRST_AID(9, "first_aid"), + FOURTH_CATEGORY(10, "fourth_category"), + THIRD_CATEGORY(11, "third_category"), + SECOND_CATEGORY(12, "second_category"), + FIRST_CATEGORY(13, "first_category"), + HORS_CATEGORY(14, "hors_category"), + SPRINT(15, "sprint"), + LEFT_FORK(16, "left_fork"), + RIGHT_FORK(17, "right_fork"), + MIDDLE_FORK(18, "middle_fork"), + SLIGHT_LEFT(19, "slight_left"), + SHARP_LEFT(20, "sharp_left"), + SLIGHT_RIGHT(21, "slight_right"), + SHARP_RIGHT(22, "sharp_right"), + U_TURN(23, "u_turn"), + SEGMENT_START(24, "segment_start"), + SEGMENT_END(25, "segment_end"), + CAMPSITE(27, "campsite"), + AID_STATION(28, "aid_station"), + REST_AREA(29, "rest_area"), + GENERAL_DISTANCE(30, "general_distance"), + SERVICE(31, "service"), + ENERGY_GEL(32, "energy_gel"), + SPORTS_DRINK(33, "sports_drink"), + MILE_MARKER(34, "mile_marker"), + CHECKPOINT(35, "checkpoint"), + SHELTER(36, "shelter"), + MEETING_SPOT(37, "meeting_spot"), + OVERLOOK(38, "overlook"), + TOILET(39, "toilet"), + SHOWER(40, "shower"), + GEAR(41, "gear"), + SHARP_CURVE(42, "sharp_curve"), + STEEP_INCLINE(43, "steep_incline"), + TUNNEL(44, "tunnel"), + BRIDGE(45, "bridge"), + OBSTACLE(46, "obstacle"), + CROSSING(47, "crossing"), + STORE(48, "store"), + TRANSITION(49, "transition"), + NAVAID(50, "navaid"), + TRANSPORT(51, "transport"), + ALERT(52, "alert"), + INFO(53, "info"); + + private final int id; + + @NonNull + private final String name; + + CoursePoint(int id, @NonNull String name) { + this.id = id; + this.name = name; + } + + @Nullable + public static CoursePoint fromId(int id) { + for (CoursePoint symbol : values()) { + if (id == symbol.id) { + return symbol; + } + } + return null; + } + + @Nullable + public static CoursePoint fromName(String name) { + for (CoursePoint symbol : values()) { + if (symbol.name.equals(name)) { + return symbol; + } + } + return null; + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + @NonNull + @Override + public String toString() { + return name; + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitCoursePoint.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitCoursePoint.java index 8221f698f4..3038d206e4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitCoursePoint.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitCoursePoint.java @@ -22,6 +22,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitRecord import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader; +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionCoursePoint.CoursePoint; /** * WARNING: This class was auto-generated, please avoid modifying it directly. @@ -60,8 +61,8 @@ public class FitCoursePoint extends RecordData { } @Nullable - public Integer getType() { - return (Integer) getFieldByNumber(5); + public CoursePoint getType() { + return (CoursePoint) getFieldByNumber(5); } @Nullable @@ -107,7 +108,7 @@ public class FitCoursePoint extends RecordData { return this; } - public Builder setType(final Integer value) { + public Builder setType(final CoursePoint value) { setFieldByNumber(5, value); return this; } diff --git a/app/src/test/resources/TestFitMessageTypeParsing.txt b/app/src/test/resources/TestFitMessageTypeParsing.txt index 71c748f1a7..5366d78e5a 100644 --- a/app/src/test/resources/TestFitMessageTypeParsing.txt +++ b/app/src/test/resources/TestFitMessageTypeParsing.txt @@ -22,7 +22,7 @@ FitWorkoutStep{2025-09-21 23:59:59.000, wkt_step_name=a, duration_type=1, durati FitSchedule{2025-09-21 23:59:59.000, manufacturer=1, product=1, serial_number=1, time_created=1127433599, completed=true, type=1, scheduled_time=1}, FitWeightScale{2025-09-21 23:59:59.000, weight=1.0, percent_fat=1.0, percent_hydration=1.0, visceral_fat_mass=1.0, bone_mass=1.0, muscle_mass=1.0, basal_met=1.0, physique_rating=1, active_met=1.0, metabolic_age=1, visceral_fat_rating=1, user_profile_index=1, bmi=1.0, timestamp=1758499199}, FitCourse{2025-09-21 23:59:59.000, sport=1, name=a, capabilities=1, sub_sport=1}, -FitCoursePoint{2025-09-21 23:59:59.000, timestamp=1758499199, position_lat=8.381903171539307E-8, position_long=8.381903171539307E-8, distance=1.0, type=1, name=a, favorite=1, message_index=1}, +FitCoursePoint{2025-09-21 23:59:59.000, timestamp=1758499199, position_lat=8.381903171539307E-8, position_long=8.381903171539307E-8, distance=1.0, type=summit, name=a, favorite=1, message_index=1}, FitTotals{2025-09-21 23:59:59.000, timer_time=1, distance=1, calories=1, sport=1, elapsed_time=1, sessions=1, active_time=1, sport_index=1, timestamp=1758499199, message_index=1}, FitActivity{2025-09-21 23:59:59.000, total_timer_time=1000, num_sessions=1, type=1, event=3, event_type=1, local_timestamp=1, event_group=1, timestamp=1758499199}, FitSoftware{2025-09-21 23:59:59.000, version=1.0, part_number=a, message_index=1},