Garmin: extend FIT file parsing 14 - COURSE_POINT 32 / type 4

based on https://github.com/muktihari/fit
This commit is contained in:
Thomas Kuehne
2025-10-10 01:01:44 +02:00
committed by José Rebelo
parent 62b324ec90
commit a28ed02534
6 changed files with 163 additions and 6 deletions
@@ -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
}
}
@@ -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")
@@ -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;
};
}
@@ -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 <https://www.gnu.org/licenses/>. */
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;
}
}
}
@@ -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;
}
@@ -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},