Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions lib/src/garmin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -365,19 +365,29 @@ GarminStatusAck? garminParseStatusAck(GarminGfdiFrame f) {
/// from.
const int kGarminEpochOffset = 631065600;

/// Build the full answer to CURRENT_TIME_REQUEST (5052): a RESPONSE (5000)
/// frame shaped as `ref_msg_type:u16(5052) | status:i8(0) | reference_id:u32(0)
/// | garmin_timestamp:u32 | utc_offset_sec:i32 | dst_end:i32(0) |
/// dst_start:i32(0)`. DST transitions are left at 0 — this pass states the
/// current UTC offset and nothing about a future change to it.
Uint8List garminBuildTimeResponse({
/// Build the full answer to a CURRENT_TIME_REQUEST (5052) [request]: a
/// RESPONSE (5000) frame shaped as `ref_msg_type:u16(5052) | status:i8(0) |
/// reference_id:u32 | garmin_timestamp:u32 | utc_offset_sec:i32 |
/// dst_end:i32(0) | dst_start:i32(0)`. `reference_id` echoes the request's
/// own payload u32 so the watch can match the answer to what it asked. Null
/// when [request] is not a 5052 frame carrying that id. DST transitions are
/// left at 0 — this pass states the current UTC offset and nothing about a
/// future change to it.
Uint8List? garminBuildTimeResponse(
GarminGfdiFrame request, {
required int nowUnixSeconds,
required int utcOffsetSeconds,
}) {
if (request.type != kGarminMsgCurrentTimeRequest ||
request.payload.length < 4) {
return null;
}
final referenceId =
ByteData.sublistView(request.payload).getUint32(0, Endian.little);
final b = ByteData(23)
..setUint16(0, kGarminMsgCurrentTimeRequest, Endian.little)
..setInt8(2, 0)
..setUint32(3, 0, Endian.little)
..setUint32(3, referenceId, Endian.little)
..setUint32(7, nowUnixSeconds - kGarminEpochOffset, Endian.little)
..setInt32(11, utcOffsetSeconds, Endian.little)
..setInt32(15, 0, Endian.little)
Expand Down
17 changes: 15 additions & 2 deletions test/garmin_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,29 @@ void main() {
test('time response carries the Garmin-epoch timestamp and UTC offset',
() {
final nowUnix = 1735689600; // 2025-01-01T00:00:00Z
final frame = garminBuildTimeResponse(
nowUnixSeconds: nowUnix, utcOffsetSeconds: 3600);
final request = garminParseGfdiFrame(garminBuildGfdiFrame(
kGarminMsgCurrentTimeRequest, [0x2a, 0x01, 0x00, 0x00]))!;
final frame = garminBuildTimeResponse(request,
nowUnixSeconds: nowUnix, utcOffsetSeconds: 3600)!;
final parsed = garminParseGfdiFrame(frame)!;
expect(parsed.type, kGarminMsgResponse);
final view = ByteData.sublistView(parsed.payload);
expect(view.getUint16(0, Endian.little), kGarminMsgCurrentTimeRequest);
expect(view.getUint32(3, Endian.little), 0x012a,
reason: 'reference_id must echo the request');
expect(view.getUint32(7, Endian.little),
nowUnix - kGarminEpochOffset);
expect(view.getInt32(11, Endian.little), 3600);
});

test('time response abstains without a request id to echo', () {
final short = garminParseGfdiFrame(
garminBuildGfdiFrame(kGarminMsgCurrentTimeRequest, [1, 2]))!;
expect(
garminBuildTimeResponse(short,
nowUnixSeconds: 1735689600, utcOffsetSeconds: 0),
isNull);
});
});

group('MLR', () {
Expand Down
Loading