From bacc85bcb8dd243aeef7f01706b11e529fcaa887 Mon Sep 17 00:00:00 2001 From: Thomas Kuehne Date: Sat, 28 Feb 2026 17:42:45 +0100 Subject: [PATCH] Garmin: add sleep disruption FIT fields --- .../devices/garmin/fit/NativeFITMessage.java | 13 +++ .../fit/messages/FitRecordDataFactory.java | 2 + .../FitSleepDisruptionOvernightSeverity.java | 80 ++++++++++++++++ .../FitSleepDisruptionSeverityPeriod.java | 90 ++++++++++++++++++ .../resources/TestFitMessageTypeParsing.fit | Bin 8907 -> 8953 bytes .../resources/TestFitMessageTypeParsing.txt | 4 +- 6 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepDisruptionOvernightSeverity.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepDisruptionSeverityPeriod.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/NativeFITMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/NativeFITMessage.java index 814c57f7f0..68db0f4243 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/NativeFITMessage.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/NativeFITMessage.java @@ -2064,6 +2064,17 @@ public class NativeFITMessage { new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP) )); + public static NativeFITMessage SLEEP_DISRUPTION_SEVERITY_PERIOD = new NativeFITMessage(470, "SLEEP_DISRUPTION_SEVERITY_PERIOD", Arrays.asList( + new FieldDefinitionPrimitive(0, BaseType.ENUM, "severity"), + new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP), + new FieldDefinitionPrimitive(254, BaseType.UINT16, "message_index") + )); + + public static NativeFITMessage SLEEP_DISRUPTION_OVERNIGHT_SEVERITY = new NativeFITMessage(471, "SLEEP_DISRUPTION_OVERNIGHT_SEVERITY", Arrays.asList( + new FieldDefinitionPrimitive(0, BaseType.ENUM, "severity"), + new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP) + )); + public static Map KNOWN_MESSAGES = new HashMap<>() {{ put(0, FILE_ID); put(1, CAPABILITIES); @@ -2211,6 +2222,8 @@ public class NativeFITMessage { put(403, ENDURANCE_SCORE); put(409, HSA_WRIST_TEMPERATURE_DATA); put(412, NAP); + put(470, SLEEP_DISRUPTION_SEVERITY_PERIOD); + put(471, SLEEP_DISRUPTION_OVERNIGHT_SEVERITY); }}; private final int number; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecordDataFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecordDataFactory.java index ee6b90dc41..e2a3d882da 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecordDataFactory.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecordDataFactory.java @@ -177,6 +177,8 @@ public class FitRecordDataFactory { case 403 -> new FitEnduranceScore(recordDefinition, recordHeader); case 409 -> new FitHsaWristTemperatureData(recordDefinition, recordHeader); case 412 -> new FitNap(recordDefinition, recordHeader); + case 470 -> new FitSleepDisruptionSeverityPeriod(recordDefinition, recordHeader); + case 471 -> new FitSleepDisruptionOvernightSeverity(recordDefinition, recordHeader); default -> new RecordData(recordDefinition, recordHeader); }; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepDisruptionOvernightSeverity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepDisruptionOvernightSeverity.java new file mode 100644 index 0000000000..1473c98c2a --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepDisruptionOvernightSeverity.java @@ -0,0 +1,80 @@ +/* Copyright (C) 2026 Freeyourgadget + + 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.messages; + +import androidx.annotation.Nullable; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitRecordDataBuilder; +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; + +/** + * WARNING: This class was auto-generated, please avoid modifying it directly. + * See {@link nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen} + * + * @noinspection unused + */ +public class FitSleepDisruptionOvernightSeverity extends RecordData { + public FitSleepDisruptionOvernightSeverity(final RecordDefinition recordDefinition, final RecordHeader recordHeader) { + super(recordDefinition, recordHeader); + + final int nativeNumber = recordDefinition.getNativeFITMessage().getNumber(); + if (nativeNumber != 471) { + throw new IllegalArgumentException("FitSleepDisruptionOvernightSeverity expects native messages of " + 471 + ", got " + nativeNumber); + } + } + + @Nullable + public Integer getSeverity() { + return getFieldByNumber(0, Integer.class); + } + + @Nullable + public Long getTimestamp() { + return getFieldByNumber(253, Long.class); + } + + /** + * @noinspection unused + */ + public static class Builder extends FitRecordDataBuilder { + public Builder() { + super(471); + } + + public Builder setSeverity(final Integer value) { + setFieldByNumber(0, value); + return this; + } + + public Builder setTimestamp(final Long value) { + setFieldByNumber(253, value); + return this; + } + + @Override + public FitSleepDisruptionOvernightSeverity build() { + return (FitSleepDisruptionOvernightSeverity) super.build(); + } + + @Override + public FitSleepDisruptionOvernightSeverity build(final int localMessageType) { + return (FitSleepDisruptionOvernightSeverity) super.build(localMessageType); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepDisruptionSeverityPeriod.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepDisruptionSeverityPeriod.java new file mode 100644 index 0000000000..028409a2f7 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepDisruptionSeverityPeriod.java @@ -0,0 +1,90 @@ +/* Copyright (C) 2026 Freeyourgadget + + 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.messages; + +import androidx.annotation.Nullable; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitRecordDataBuilder; +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; + +/** + * WARNING: This class was auto-generated, please avoid modifying it directly. + * See {@link nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen} + * + * @noinspection unused + */ +public class FitSleepDisruptionSeverityPeriod extends RecordData { + public FitSleepDisruptionSeverityPeriod(final RecordDefinition recordDefinition, final RecordHeader recordHeader) { + super(recordDefinition, recordHeader); + + final int nativeNumber = recordDefinition.getNativeFITMessage().getNumber(); + if (nativeNumber != 470) { + throw new IllegalArgumentException("FitSleepDisruptionSeverityPeriod expects native messages of " + 470 + ", got " + nativeNumber); + } + } + + @Nullable + public Integer getSeverity() { + return getFieldByNumber(0, Integer.class); + } + + @Nullable + public Long getTimestamp() { + return getFieldByNumber(253, Long.class); + } + + @Nullable + public Integer getMessageIndex() { + return getFieldByNumber(254, Integer.class); + } + + /** + * @noinspection unused + */ + public static class Builder extends FitRecordDataBuilder { + public Builder() { + super(470); + } + + public Builder setSeverity(final Integer value) { + setFieldByNumber(0, value); + return this; + } + + public Builder setTimestamp(final Long value) { + setFieldByNumber(253, value); + return this; + } + + public Builder setMessageIndex(final Integer value) { + setFieldByNumber(254, value); + return this; + } + + @Override + public FitSleepDisruptionSeverityPeriod build() { + return (FitSleepDisruptionSeverityPeriod) super.build(); + } + + @Override + public FitSleepDisruptionSeverityPeriod build(final int localMessageType) { + return (FitSleepDisruptionSeverityPeriod) super.build(localMessageType); + } + } +} diff --git a/app/src/test/resources/TestFitMessageTypeParsing.fit b/app/src/test/resources/TestFitMessageTypeParsing.fit index 2899f18f544d9bca80c724c8a302bd0239533b05..4f65740ec69f26b1c03c7a165e4aa13e717d49ec 100644 GIT binary patch delta 623 zcmYjOJ!q3*5WeTWH~GH2i3!cuwA#>E+WLp3DDf|Y8njVsmqsFmF18XVRB+S9!47Xf z;wGH7D6MuEM9HQgQL7b;XggSNDUwAe!A+r2-bTec4vvF+e(shFKML`9*V}VnyNUWw z4VG?xdK|Ao)-a-XL+^2PABf}t_l1C84agYRF)vH1C=+T-+Eh`Nogum8jO~4*Do<6V z27V5H0e%TyWb+FA8oUI41OC8fh0Q7(lBw`fIUF98ccDpH^m=3=v|p}z?Q%Ah%@9o; zw*?ynl*?tZnJ4n;n$+E*+;B(bpj(p9;nMcosxqT0vuy6D%3W2N1J8r+vANG?p-GjJ z_kO>8^DfGQH!O$zPPy#u>-tASf4l#PTB#&CJu2&wQJIaL5gk1tt2!sQ^sx(hq7?ZX zDj=(ln2n)1|f!a9RbOpbV%7 w)C=BWscPwLr2NIP!lA3*(*xn1AK^6EG|xfUbca0A8u-s_)0kwtXZYc>-{MD?S^xk5 delta 536 zcmXYr&1(|@5XEO^C%f5AOfj2?NouOGN-IT$Aozhc+NLNLOzLVdDkQMrNkn?^ps0t< zl52U02s!u<2uko`E>*D&2*r<^;Hj6MyeWFH%35c5yu+K{@P1X_S5v9o{e?k^=-}Kr zZ{@^`)H2%>>`WTY6cg6CVG{G7O~xUaLQB>sdatxQ(C#wJYnC@GZ&}{4tgx)I3|T%% z@KJ(K5@1PNnA7w4Zq)I}XkpfNV`~{p`JH&` zCjU#Z9-BnP&ERJ22u5Qk%?goAcTtEJv1)l(v^?ajCWdC63HR(g*4#17+lTQrzJUAj zHq@;|P=-9zoU{00H_@}3NGGQ8#W{v2&gm%5l8al3Jx70QNKDKm5*QjPizPu^0@~{S zpjw_`cAUr!W@4h~m_IE-B{)Q1XW`{8-l>0vmGba2J