alex-hhh/ActivityLog2

Lap swimming: TRACKPOINTS are linked to wrong LENGTHS

idealist1508 opened this issue · 19 comments

I was trying to fix #4 and found that my HR data isn't imported correctly.
The marked HR data belongs to another LENGTH. Current LENGTH ended at ...3123.5
swimlap1

Code to reproduce:

#lang racket
(require "../rkt/fit-file/fit-file.rkt")

(read-activity-from-file "2021-08-20-20-00-58.fit")      

fit file

Can you check if the patch below fixes the issue? If it works, I will need to clean it up and also run it against the other tests in my test suite just to be sure. Unfortunately, the FIT specification is a bit ambiguous on how to match records to lengths and laps and different devices generate slightly different code.

Also, this is probably the first serious Racket code that I wrote and it looks a bit ugly :-)

 rkt/fit-file/fit-file.rkt | 65 +++++++++++++++++++++++++++++++++--------------
 1 file changed, 46 insertions(+), 19 deletions(-)

diff --git a/rkt/fit-file/fit-file.rkt b/rkt/fit-file/fit-file.rkt
index 91608bd..48431ed 100644
--- a/rkt/fit-file/fit-file.rkt
+++ b/rkt/fit-file/fit-file.rkt
@@ -2,7 +2,7 @@
 ;; fit-file.rkt -- read and write .FIT files.
 
 ;; This file is part of ActivityLog2, an fitness activity tracker
-;; Copyright (C) 2015, 2018, 2019, 2020 Alex Harsányi <AlexHarsanyi@gmail.com>
+;; Copyright (C) 2015, 2018, 2019, 2020, 2021 Alex Harsányi <AlexHarsanyi@gmail.com>
 ;;
 ;; This program is free software: you can redistribute it and/or modify it
 ;; under the terms of the GNU General Public License as published by the Free
@@ -988,26 +988,53 @@
                       data))
 
                     (#t
+                     ;; NOTE: in general, start-time is the time when an even
+                     ;; occurred, while timestamp is the time time when it was
+                     ;; written to the FIT file.  Garmin devices stick to this
+                     ;; convention, but other devices do not...
+                     (define (key-fn e)
+                       (or (dict-ref e 'start-time #f)
+                           (dict-ref e 'timestamp #f)))
+
+                     (define (add-length-records length records)
+                       (define start-time (dict-ref length 'start-time #f))
+                       (define timer (dict-ref length 'total-timer-time #f))
+                       (define elapsed (dict-ref length 'total-elapsed-time #f))
+                       ;; The FIT specification mentions that timer-time
+                       ;; EXCLUDES pauses and elapsed-time INCLUDES pauses, so
+                       ;; we should have elapsed-time greater or equal to
+                       ;; timer-time, however some FIT files have the two
+                       ;; swapped, so we select the larger of the two as the
+                       ;; session duration.
+                       (define duration
+                         (cond ((and timer elapsed) (max timer elapsed))
+                               (timer)
+                               (elapsed)
+                               (#t #f)))
+                       (define end-time
+                         (or
+                          ;; If we have a start time and an elapsed time, we use that
+                          (and start-time duration (exact-truncate (+ start-time duration)))
+                          ;; Otherwise we use the timestamp...
+                          (dict-ref length 'timestamp #f)
+                          ;; Or the last timestamp seen in any type of record.
+                          (get-current-timestamp)))
+
+                       (define our-records
+                         (for/list ([r (in-list records)]
+                                    #:when (let ([timestamp (key-fn r)])
+                                             (and (>= timestamp start-time) (< timestamp end-time))))
+                           r))
+
+                       (cons (cons 'track our-records) length))
+
                      ;; Most generic case, use the timestamp field to assign
                      ;; records to the corresponding lengths.
-                     (let ((records (sort records < #:key (lambda (e) (dict-ref e 'timestamp #f))))
-                           (lengths (sort lengths < #:key (lambda (e) (dict-ref e 'timestamp #f)))))
-
-                       (define (add-length-records length)
-                         (let ((timestamp (dict-ref length 'timestamp #f)))
-                           (let-values ([(our-records rest)
-                                         (splitf-at records
-                                                    (lambda (v)
-                                                      (<= (dict-ref v 'timestamp #f) timestamp)))])
-                             (set! records rest)         ; will be used by the next length
-                             (cons (cons 'track our-records) length))))
-
-                       (let ((data (cons
-                                    (cons 'lengths (map add-length-records lengths))
-                                    data)))
-                         (when (> (length records) 0)
-                           (dbglog "fit-file: remaining records after processing LENGTHS"))
-                         data)))))
+                     (let ((records (sort records < #:key key-fn))
+                           (lengths (sort lengths < #:key key-fn)))
+                       (define nlengths (for/list ([l (in-list lengths)])
+                                          (add-length-records l records)))
+                       (cons (cons 'lengths nlengths) data)))))
 
         (set! records '())
         (set! lengths '())

Thanks, It looks pretty good, but I noticed some issues

  1. Trackpoint with a start-time ...2565 should belong to a previous length.

swimlap_fix1

  1. Trackpoint with a start-time ...2622 is missing. (I think it is actually the same issue )
    swimlap_fix2

I think the problem is the use of exact-truncate in the line below. Removing it should fix that... I copied that code from another place, I will need to revisit the use of exact-truncate there as well.

(and start-time duration (exact-truncate (+ start-time duration)))

Removing it produces another issue. The same trackpoint is linked sometimes to both length.
I am not sure what is a better solution.

I think, that both issues (lost or duplicated trackpoint) doesn't matter in lap swimming a lot.

  1. Trackpoints contains only HR data in lap swimming.
  2. I think it is sufficient to take an average to fix #4. (so one "wrong" value doesn't matter a lot)

I think the problem is that the FIT file itself is ambiguous because timestamps and start times are stored as integer number of seconds.

For example we have one lap starting at 1629482511 with a duration of 54.125 seconds, so the lap ends at 1629482565.125, while the next lap starts at 1629482565, there is an 0.125 second overlap between the two laps. To which lap should the record with a timestamp 1629482565 belong?

On the other hand the we have another lap that starts at 1629482565 with a duration of 57.687, so it ends at 1629482622.687, but the next lap starts at 1629482623, so there is a gap of 0.313 seconds between laps.

Sure, it is ambiguous because format of timestamps

It is possible to correct it as following:

  • The first length has a start time from fit file, end time = start time + duration

  • Each next length has a same start time as end time of the previous length, end time = start time + duration

  • Last length has has a same start time as end time of the previous length, end time = round up (start time + duration)

I don't know how expensive to implement it and whether it is worth.

as example the calculation with "Excel"
grafik

I found a way to assign all records to the correct length without loosing any of them, so I believe this is fixed now.

Closing issue, let me know if this is still a problem

Hi, I noticed, that some hr-trackpoints are lost now.
I think it has something to do with the order of the records .
i debugged a fit file and it looks like they are stored like this:

trackpoints for lap 1
trackpoints for lap 2
lap 1
lap 2

Here is my debugger sesstion. I made a breakpoint in

(when (> (length records) 0)

Line 1 and Line 2 are records from Lap 1
Line 3 is a data from Lap3

 ### DEBUGGER: "records = (((start-time . 1636650413) (timestamp . 1636650413) (88 . 300) (heart-rate . 119)) ((start-time . 1636650414) (timestamp . 1636650414) (88 . 300) (heart-rate . 118)) ((start-time . 1636650415) (timestamp . 1636650415) (88 . 300) (heart-rate . 118)) ((start-time . 1636650416) (timestamp . 1636650416) (88 . 300) (heart-rate . 118)) ((start-time . 1636650417) (timestamp . 1636650417) (88 . 300) (heart-rate . 118)) ((start-time . 1636650418) (timestamp . 1636650418) (88 . 300) (heart-rate . 117)) ((start-time . 1636650419) (timestamp . 1636650419) (88 . 300) (heart-rate . 116)) ((start-time . 1636650420) (timestamp . 1636650420) (88 . 300) (heart-rate . 116)) ((start-time . 1636650421) (timestamp . 1636650421) (88 . 300) (heart-rate . 115)) ((start-time . 1636650422) (timestamp . 1636650422) (88 . 300) (heart-rate . 115)) ((start-time . 1636650423) (timestamp . 1636650423) (88 . 300) (heart-rate . 115)) ((start-time . 1636650424) (timestamp . 1636650424) (88 . 300) (heart-rate . 115)) ((start-time . 1636650425) (timestamp . 1636650425) (88 . 300) (heart-rate . 115)) ((start-time . 1636650426) (timestamp . 1636650426) (88 . 300) (heart-rate . 114)) ((start-time . 1636650427) (timestamp . 1636650427) (88 . 300) (heart-rate . 113)) ((start-time . 1636650428) (timestamp . 1636650428) (88 . 300) (heart-rate . 113)) ((start-time . 1636650429) (timestamp . 1636650429) (88 . 300) (heart-rate . 113)))"
 ### DEBUGGER: "result = (((track ((start-time . 1636650391) (timestamp . 1636650391) (88 . 300) (heart-rate . 134)) ((start-time . 1636650392) (timestamp . 1636650392) (88 . 300) (heart-rate . 133)) ((start-time . 1636650393) (timestamp . 1636650393) (88 . 300) (heart-rate . 132)) ((start-time . 1636650394) (timestamp . 1636650394) (88 . 300) (heart-rate . 131)) ((start-time . 1636650395) (timestamp . 1636650395) (88 . 300) (heart-rate . 129)) ((start-time . 1636650396) (timestamp . 1636650396) (88 . 300) (heart-rate . 128)) ((start-time . 1636650397) (timestamp . 1636650397) (88 . 300) (heart-rate . 126)) ((start-time . 1636650398) (timestamp . 1636650398) (88 . 300) (heart-rate . 126)) ((start-time . 1636650399) (timestamp . 1636650399) (88 . 300) (heart-rate . 126)) ((start-time . 1636650400) (timestamp . 1636650400) (88 . 300) (heart-rate . 125)) ((start-time . 1636650401) (timestamp . 1636650401) (88 . 300) (heart-rate . 124)) ((start-time . 1636650402) (timestamp . 1636650402) (88 . 300) (heart-rate . 123)) ((start-time . 1636650403) (timestamp . 1636650403) (88 . 300) (heart-rate . 123)) ((start-time . 1636650404) (timestamp . 1636650404) (88 . 300) (heart-rate . 122)) ((start-time . 1636650405) (timestamp . 1636650405) (88 . 300) (heart-rate . 122)) ((start-time . 1636650406) (timestamp . 1636650406) (88 . 300) (heart-rate . 122)) ((start-time . 1636650407) (timestamp . 1636650407) (88 . 300) (heart-rate . 121)) ((start-time . 1636650408) (timestamp . 1636650408) (88 . 300) (heart-rate . 119)) ((start-time . 1636650409) (timestamp . 1636650409) (88 . 300) (heart-rate . 119)) ((start-time . 1636650410) (timestamp . 1636650410) (88 . 300) (heart-rate . 119)) ((start-time . 1636650411) (timestamp . 1636650411) (88 . 300) (heart-rate . 119)) ((start-time . 1636650412) (timestamp . 1636650412) (88 . 300) (heart-rate . 119))) (length-type . active) (start-time . 1636650390) (avg-cadence . 21) (total-cycles . 8) (avg-speed . 1.114) (timestamp . 1636650429) (total-elapsed-time . 22.437) (total-timer-time . 22.437) (message-index . 14) (total-strokes . 8) (total-calories . 8) (event . length) (event-type . stop) (avg-swimming-cadence . 21) (length-type . active) (max-heart-rate . 134) (avg-heart-rate . 124.54761904761907)) ((track ((start-time . 1636650362) (timestamp . 1636650362) (88 . 300) (heart-rate . 128)) ((start-time . 1636650363) (timestamp . 1636650363) (88 . 300) (heart-rate . 128)) ((start-time . 1636650364) (timestamp . 1636650364) (88 . 300) (heart-rate . 128)) ((start-time . 1636650365) (timestamp . 1636650365) (88 . 300) (heart-rate . 128)) ((start-time . 1636650366) (timestamp . 1636650366) (88 . 300) (heart-rate . 129)) ((start-time . 1636650367) (timestamp . 1636650367) (88 . 300) (heart-rate . 129)) ((start-time . 1636650368) (timestamp . 1636650368) (88 . 300) (heart-rate . 129)) ((start-time . 1636650369) (timestamp . 1636650369) (88 . 300) (heart-rate . 128)) ((start-time . 1636650370) (timestamp . 1636650370) (88 . 300) (heart-rate . 131)) ((start-time . 1636650371) (timestamp . 1636650371) (88 . 300) (heart-rate . 133)) ((start-time . 1636650372) (timestamp . 1636650372) (88 . 300) (heart-rate . 132)) ((start-time . 1636650373) (timestamp . 1636650373) (88 . 300) (heart-rate . 132)) ((start-time . 1636650374) (timestamp . 1636650374) (88 . 300) (heart-rate . 132)) ((start-time . 1636650375) (timestamp . 1636650375) (88 . 300) (heart-rate . 132)) ((start-time . 1636650376) (timestamp . 1636650376) (88 . 300) (heart-rate . 131)) ((start-time . 1636650377) (timestamp . 1636650377) (88 . 300) (heart-rate . 129)) ((start-time . 1636650378) (timestamp . 1636650378) (88 . 300) (heart-rate . 128)) ((start-time . 1636650379) (timestamp . 1636650379) (88 . 300) (heart-rate . 127)) ((start-time . 1636650380) (timestamp . 1636650380) (88 . 300) (heart-rate . 129)) ((start-time . 1636650381) (timestamp . 1636650381) (88 . 300) (heart-rate . 131)) ((start-time . 1636650382) (timestamp . 1636650382) (88 . 300) (heart-rate . 133)) ((start-time . 1636650383) (timestamp . 1636650383) (88 . 300) (heart-rate . 133)) ((start-time . 1636650384) (timestamp . 1636650384) (88 . 300) (heart-rate . 133)) ((start-time . 1636650385) (timestamp . 1636650385) (88 . 300) (heart-rate . 134)) ((start-time . 1636650386) (timestamp . 1636650386) (88 . 300) (heart-rate . 134)) ((start-time . 1636650387) (timestamp . 1636650387) (88 . 300) (heart-rate . 134)) ((start-time . 1636650388) (timestamp . 1636650388) (88 . 300) (heart-rate . 136)) ((start-time . 1636650389) (timestamp . 1636650389) (88 . 300) (heart-rate . 135)) ((start-time . 1636650390) (timestamp . 1636650390) (88 . 300) (heart-rate . 135))) (length-type . active) (start-time . 1636650362) (avg-cadence . 19) (total-cycles . 9) (avg-speed . 0.875) (timestamp . 1636650429) (total-elapsed-time . 28.562) (total-timer-time . 28.562) (message-index . 13) (total-strokes . 9) (total-calories . 8) (event . length) (event-type . stop) (avg-swimming-cadence . 19) (length-type . active) (max-heart-rate . 136) (avg-heart-rate . 131.05357142857144)) ((track ((start-time . 1636650346) (timestamp . 1636650346) (88 . 300) (heart-rate . 135)) ((start-time . 1636650347) (timestamp . 1636650347) (88 . 300) (heart-rate . 134)) ((start-time . 1636650348) (timestamp . 1636650348) (88 . 300) (heart-rate . 134)) ((start-time . 1636650349) (timestamp . 1636650349) (88 . 300) (heart-rate . 133)) ((start-time . 1636650350) (timestamp . 1636650350) (88 . 300) (heart-rate . 134)) ((start-time . 1636650351) (timestamp . 1636650351) (88 . 300) (heart-rate . 133)) ((start-time . 1636650352) (timestamp . 1636650352) (88 . 300) (heart-rate . 134)) ((start-time . 1636650353) (timestamp . 1636650353) (88 . 300) (heart-rate . 134)) ((start-time . 1636650354) (timestamp . 1636650354) (88 . 300) (heart-rate . 133)) ((start-time . 1636650355) (timestamp . 1636650355) (88 . 300) (heart-rate . 133)) ((start-time . 1636650356) (timestamp . 1636650356) (88 . 300) (heart-rate . 133)) ((start-time . 1636650357) (timestamp . 1636650357) (88 . 300) (heart-rate . 132)) ((start-time . 1636650358) (timestamp . 1636650358) (88 . 300) (heart-rate . 130)) ((start-time . 1636650359) (timestamp . 1636650359) (88 . 300) (heart-rate . 129)) ((start-time . 1636650360) (timestamp . 1636650360) (88 . 300) (heart-rate . 128)) ((start-time . 1636650361) (timestamp . 1636650361) (88 . 300) (heart-rate . 128))) (length-type . active) (start-time . 1636650345) (avg-cadence . 22) (total-cycles . 6) (avg-speed . 1.521) (timestamp . 1636650429) (total-elapsed-time . 16.437) (total-timer-time . 16.437) (message-index . 12) (total-strokes . 6) (total-calories . 9) (event . length) (event-type . stop) (avg-swimming-cadence . 22) (length-type . active) (max-heart-rate . 135) (avg-heart-rate . 132.36666666666665)) ((track ((start-time . 1636650327) (timestamp . 1636650327) (88 . 300) (heart-rate . 134)) ((start-time . 1636650328) (timestamp . 1636650328) (88 . 300) (heart-rate . 134)) ((start-time . 1636650329) (timestamp . 1636650329) (88 . 300) (heart-rate . 133)) ((start-time . 1636650330) (timestamp . 1636650330) (88 . 300) (heart-rate . 133)) ((start-time . 1636650331) (timestamp . 1636650331) (88 . 300) (heart-rate . 133)) ((start-time . 1636650332) (timestamp . 1636650332) (88 . 300) (heart-rate . 133)) ((start-time . 1636650333) (timestamp . 1636650333) (88 . 300) (heart-rate . 133)) ((start-time . 1636650334) (timestamp . 1636650334) (88 . 300) (heart-rate . 133)) ((start-time . 1636650335) (timestamp . 1636650335) (88 . 300) (heart-rate . 132)) ((start-time . 1636650336) (timestamp . 1636650336) (88 . 300) (heart-rate . 133)) ((start-time . 1636650337) (timestamp . 1636650337) (88 . 300) (heart-rate . 134)) ((start-time . 1636650338) (timestamp . 1636650338) (88 . 300) (heart-rate . 135)) ((start-time . 1636650339) (timestamp . 1636650339) (88 . 300) (heart-rate . 135)) ((start-time . 1636650340) (timestamp . 1636650340) (88 . 300) (heart-rate . 135)) ((start-time . 1636650341) (timestamp . 1636650341) (88 . 300) (heart-rate . 135)) ((start-time . 1636650342) (timestamp . 1636650342) (88 . 300) (heart-rate . 136)) ((start-time . 1636650343) (timestamp . 1636650343) (88 . 300) (heart-rate . 136)) ((start-time . 1636650344) (timestamp . 1636650344) (88 . 300) (heart-rate . 136)) ((start-time . 1636650345) (timestamp . 1636650345) (88 . 300) (heart-rate . 135))) (length-type . active) (start-time . 1636650327) (avg-cadence . 26) (total-cycles . 8) (avg-speed . 1.365) (timestamp . 1636650350) (total-elapsed-time . 18.312) (total-timer-time . 18.312) (message-index . 11) (total-strokes . 8) (total-calories . 6) (event . length) (event-type . stop) (avg-swimming-cadence . 26) (length-type . active) (max-heart-rate . 136) (avg-heart-rate . 134.08333333333334)) ((track ((start-time . 1636650314) (timestamp . 1636650314) (88 . 300) (heart-rate . 128)) ((start-time . 1636650315) (timestamp . 1636650315) (88 . 300) (heart-rate . 128)) ((start-time . 1636650316) (timestamp . 1636650316) (88 . 300) (heart-rate . 128)) ((start-time . 1636650317) (timestamp . 1636650317) (88 . 300) (heart-rate . 127)) ((start-time . 1636650318) (timestamp . 1636650318) (88 . 300) (heart-rate . 127)) ((start-time . 1636650319) (timestamp . 1636650319) (88 . 300) (heart-rate . 128)) ((start-time . 1636650320) (timestamp . 1636650320) (88 . 300) (heart-rate . 129)) ((start-time . 1636650321) (timestamp . 1636650321) (88 . 300) (heart-rate . 129)) ((start-time . 1636650322) (timestamp . 1636650322) (88 . 300) (heart-rate . 128)) ((start-time . 1636650323) (timestamp . 1636650323) (88 . 300) (heart-rate . 130)) ((start-time . 1636650324) (timestamp . 1636650324) (88 . 300) (heart-rate . 131)) ((start-time . 1636650325) (timestamp . 1636650325) (88 . 300) (heart-rate . 133)) ((start-time . 1636650326) (timestamp . 1636650326) (88 . 300) (heart-rate . 133))) (length-type . active) (start-time . 1636650313) (avg-cadence . 26) (total-cycles . 6) (avg-speed . 1.794) (timestamp . 1636650350) (total-elapsed-time . 13.937) (total-timer-time . 13.937) (message-index . 10) (total-strokes . 6) (total-calories . 9) (event . length) (event-type . stop) (avg-swimming-cadence . 26) (length-type . active) (max-heart-rate . 133) (avg-heart-rate . 129.04166666666663)) ((track ((start-time . 1636650290) (timestamp . 1636650290) (88 . 300) (heart-rate . 129)) ((start-time . 1636650291) (timestamp . 1636650291) (88 . 300) (heart-rate . 129)) ((start-time . 1636650292) (timestamp . 1636650292) (88 . 300) (heart-rate . 129)) ((start-time . 1636650293) (timestamp . 1636650293) (88 . 300) (heart-rate . 129)) ((start-time . 1636650294) (timestamp . 1636650294) (88 . 300) (heart-rate . 129)) ((start-time . 1636650295) (timestamp . 1636650295) (88 . 300) (heart-rate . 129)) ((start-time . 1636650296) (timestamp . 1636650296) (88 . 300) (heart-rate . 129)) ((start-time . 1636650297) (timestamp . 1636650297) (88 . 300) (heart-rate . 130)) ((start-time . 1636650298) (timestamp . 1636650298) (88 . 300) (heart-rate . 130)) ((start-time . 1636650299) (timestamp . 1636650299) (88 . 300) (heart-rate . 129)) ((start-time . 1636650300) (timestamp . 1636650300) (88 . 300) (heart-rate . 129)) ((start-time . 1636650301) (timestamp . 1636650301) (88 . 300) (heart-rate . 128)) ((start-time . 1636650302) (timestamp . 1636650302) (88 . 300) (heart-rate . 128)) ((start-time . 1636650303) (timestamp . 1636650303) (88 . 300) (heart-rate . 128)) ((start-time . 1636650304) (timestamp . 1636650304) (88 . 300) (heart-rate . 128)) ((start-time . 1636650305) (timestamp . 1636650305) (88 . 300) (heart-rate . 129)) ((start-time . 1636650306) (timestamp . 1636650306) (88 . 300) (heart-rate . 129)) ((start-time . 1636650307) (timestamp . 1636650307) (88 . 300) (heart-rate . 129)) ((start-time . 1636650308) (timestamp . 1636650308) (88 . 300) (heart-rate . 129)) ((start-time . 1636650309) (timestamp . 1636650309) (88 . 300) (heart-rate . 129)) ((start-time . 1636650310) (timestamp . 1636650310) (88 . 300) (heart-rate . 129)) ((start-time . 1636650311) (timestamp . 1636650311) (88 . 300) (heart-rate . 129)) ((start-time . 1636650312) (timestamp . 1636650312) (88 . 300) (heart-rate . 128)) ((start-time . 1636650313) (timestamp . 1636650313) (88 . 300) (heart-rate . 127))) (length-type . active) (start-time . 1636650290) (avg-cadence . 26) (total-cycles . 10) (avg-speed . 1.07) (timestamp . 1636650350) (total-elapsed-time . 23.375) (total-timer-time . 23.375) (message-index . 9) (total-strokes . 10) (total-calories . 8) (event . length) (event-type . stop) (avg-swimming-cadence . 26) (length-type . active) (max-heart-rate . 130) (avg-heart-rate . 128.8260869565217)) ((track ((start-time . 1636650267) (timestamp . 1636650267) (88 . 300) (heart-rate . 131)) ((start-time . 1636650268) (timestamp . 1636650268) (88 . 300) (heart-rate . 131)) ((start-time . 1636650269) (timestamp . 1636650269) (88 . 300) (heart-rate . 130)) ((start-time . 1636650270) (timestamp . 1636650270) (88 . 300) (heart-rate . 129)) ((start-time . 1636650271) (timestamp . 1636650271) (88 . 300) (heart-rate . 129)) ((start-time . 1636650272) (timestamp . 1636650272) (88 . 300) (heart-rate . 129)) ((start-time . 1636650273) (timestamp . 1636650273) (88 . 300) (heart-rate . 128)) ((start-time . 1636650274) (timestamp . 1636650274) (88 . 300) (heart-rate . 128)) ((start-time . 1636650275) (timestamp . 1636650275) (88 . 300) (heart-rate . 129)) ((start-time . 1636650276) (timestamp . 1636650276) (88 . 300) (heart-rate . 129)) ((start-time . 1636650277) (timestamp . 1636650277) (88 . 300) (heart-rate . 129)) ((start-time . 1636650278) (timestamp . 1636650278) (88 . 300) (heart-rate . 129)) ((start-time . 1636650279) (timestamp . 1636650279) (88 . 300) (heart-rate . 129)) ((start-time . 1636650280) (timestamp . 1636650280) (88 . 300) (heart-rate . 129)) ((start-time . 1636650281) (timestamp . 1636650281) (88 . 300) (heart-rate . 128)) ((start-time . 1636650282) (timestamp . 1636650282) (88 . 300) (heart-rate . 128)) ((start-time . 1636650283) (timestamp . 1636650283) (88 . 300) (heart-rate . 129)) ((start-time . 1636650284) (timestamp . 1636650284) (88 . 300) (heart-rate . 129)) ((start-time . 1636650285) (timestamp . 1636650285) (88 . 300) (heart-rate . 129)) ((start-time . 1636650286) (timestamp . 1636650286) (88 . 300) (heart-rate . 129)) ((start-time . 1636650287) (timestamp . 1636650287) (88 . 300) (heart-rate . 129)) ((start-time . 1636650288) (timestamp . 1636650288) (88 . 300) (heart-rate . 129)) ((start-time . 1636650289) (timestamp . 1636650289) (88 . 300) (heart-rate . 129))) (length-type . active) (start-time . 1636650267) (avg-cadence . 29) (total-cycles . 11) (avg-speed . 1.09) (timestamp . 1636650350) (total-elapsed-time . 22.937) (total-timer-time . 22.937) (message-index . 8) (total-strokes . 11) (total-calories . 8) (event . length) (event-type . stop) (avg-swimming-cadence . 29) (length-type . active) (max-heart-rate . 131) (avg-heart-rate . 128.99999999999994)) ((track ((start-time . 1636650253) (timestamp . 1636650253) (88 . 300) (heart-rate . 137)) ((start-time . 1636650254) (timestamp . 1636650254) (88 . 300) (heart-rate . 136)) ((start-time . 1636650255) (timestamp . 1636650255) (88 . 300) (heart-rate . 134)) ((start-time . 1636650256) (timestamp . 1636650256) (88 . 300) (heart-rate . 134)) ((start-time . 1636650257) (timestamp . 1636650257) (88 . 300) (heart-rate . 133)) ((start-time . 1636650258) (timestamp . 1636650258) (88 . 300) (heart-rate . 132)) ((start-time . 1636650259) (timestamp . 1636650259) (88 . 300) (heart-rate . 132)) ((start-time . 1636650260) (timestamp . 1636650260) (88 . 300) (heart-rate . 132)) ((start-time . 1636650261) (timestamp . 1636650261) (88 . 300) (heart-rate . 132)) ((start-time . 1636650262) (timestamp . 1636650262) (88 . 300) (heart-rate . 132)) ((start-time . 1636650263) (timestamp . 1636650263) (88 . 300) (heart-rate . 132)) ((start-time . 1636650264) (timestamp . 1636650264) (88 . 300) (heart-rate . 132)) ((start-time . 1636650265) (timestamp . 1636650265) (88 . 300) (heart-rate . 132)) ((start-time . 1636650266) (timestamp . 1636650266) (88 . 300) (heart-rate . 132))) (length-type . active) (start-time . 1636650253) (avg-cadence . 22) (total-cycles . 5) (avg-speed . 1.794) (timestamp . 1636650350) (total-elapsed-time . 13.937) (total-timer-time . 13.937) (message-index . 7) (total-strokes . 5) (total-calories . 6) (event . length) (event-type . stop) (avg-swimming-cadence . 22) (length-type . active) (max-heart-rate . 137) (avg-heart-rate . 132.88461538461536)) ((track ((start-time . 1636650239) (timestamp . 1636650239) (88 . 300) (heart-rate . 149)) ((start-time . 1636650240) (timestamp . 1636650240) (88 . 300) (heart-rate . 149)) ((start-time . 1636650241) (timestamp . 1636650241) (88 . 300) (heart-rate . 149)) ((start-time . 1636650242) (timestamp . 1636650242) (88 . 300) (heart-rate . 148)) ((start-time . 1636650243) (timestamp . 1636650243) (88 . 300) (heart-rate . 147)) ((start-time . 1636650244) (timestamp . 1636650244) (88 . 300) (heart-rate . 147)) ((start-time . 1636650245) (timestamp . 1636650245) (88 . 300) (heart-rate . 146)) ((start-time . 1636650246) (timestamp . 1636650246) (88 . 300) (heart-rate . 145)) ((start-time . 1636650247) (timestamp . 1636650247) (88 . 300) (heart-rate . 144)) ((start-time . 1636650248) (timestamp . 1636650248) (88 . 300) (heart-rate . 142)) ((start-time . 1636650249) (timestamp . 1636650249) (88 . 300) (heart-rate . 139)) ((start-time . 1636650250) (timestamp . 1636650250) (88 . 300) (heart-rate . 139)) ((start-time . 1636650251) (timestamp . 1636650251) (88 . 300) (heart-rate . 138)) ((start-time . 1636650252) (timestamp . 1636650252) (88 . 300) (heart-rate . 137))) (length-type . active) (start-time . 1636650238) (avg-cadence . 21) (total-cycles . 5) (avg-speed . 1.709) (timestamp . 1636650350) (total-elapsed-time . 14.625) (total-timer-time . 14.625) (message-index . 6) (total-strokes . 5) (total-calories . 9) (event . length) (event-type . stop) (avg-swimming-cadence . 21) (length-type . active) (max-heart-rate . 149) (avg-heart-rate . 144.30769230769232)) ((track ((start-time . 1636650209) (timestamp . 1636650209) (88 . 300) (heart-rate . 145)) ((start-time . 1636650210) (timestamp . 1636650210) (88 . 300) (heart-rate . 146)) ((start-time . 1636650211) (timestamp . 1636650211) (88 . 300) (heart-rate . 147)) ((start-time . 1636650212) (timestamp . 1636650212) (88 . 300) (heart-rate . 147)) ((start-time . 1636650213) (timestamp . 1636650213) (88 . 300) (heart-rate . 147)) ((start-time . 1636650214) (timestamp . 1636650214) (88 . 300) (heart-rate . 148)) ((start-time . 1636650215) (timestamp . 1636650215) (88 . 300) (heart-rate . 148)) ((start-time . 1636650216) (timestamp . 1636650216) (88 . 300) (heart-rate . 147)) ((start-time . 1636650217) (timestamp . 1636650217) (88 . 300) (heart-rate . 147)) ((start-time . 1636650218) (timestamp . 1636650218) (88 . 300) (heart-rate . 147)) ((start-time . 1636650219) (timestamp . 1636650219) (88 . 300) (heart-rate . 149)) ((start-time . 1636650220) (timestamp . 1636650220) (88 . 300) (heart-rate . 150)) ((start-time . 1636650221) (timestamp . 1636650221) (88 . 300) (heart-rate . 151)) ((start-time . 1636650222) (timestamp . 1636650222) (88 . 300) (heart-rate . 151)) ((start-time . 1636650223) (timestamp . 1636650223) (88 . 300) (heart-rate . 150)) ((start-time . 1636650224) (timestamp . 1636650224) (88 . 300) (heart-rate . 151)) ((start-time . 1636650225) (timestamp . 1636650225) (88 . 300) (heart-rate . 151)) ((start-time . 1636650226) (timestamp . 1636650226) (88 . 300) (heart-rate . 152)) ((start-time . 1636650227) (timestamp . 1636650227) (88 . 300) (heart-rate . 153)) ((start-time . 1636650228) (timestamp . 1636650228) (88 . 300) (heart-rate . 153)) ((start-time . 1636650229) (timestamp . 1636650229) (88 . 300) (heart-rate . 153)) ((start-time . 1636650230) (timestamp . 1636650230) (88 . 300) (heart-rate . 154)) ((start-time . 1636650231) (timestamp . 1636650231) (88 . 300) (heart-rate . 154)) ((start-time . 1636650232) (timestamp . 1636650232) (88 . 300) (heart-rate . 154)) ((start-time . 1636650233) (timestamp . 1636650233) (88 . 300) (heart-rate . 153)) ((start-time . 1636650234) (timestamp . 1636650234) (88 . 300) (heart-rate . 152)) ((start-time . 1636650235) (timestamp . 1636650235) (88 . 300) (heart-rate . 152)) ((start-time . 1636650236) (timestamp . 1636650236) (88 . 300) (heart-rate . 151)) ((start-time . 1636650237) (timestamp . 1636650237) (88 . 300) (heart-rate . 150)) ((start-time . 1636650238) (timestamp . 1636650238) (88 . 300) (heart-rate . 150))) (length-type . active) (start-time . 1636650209) (avg-cadence . 32) (total-cycles . 16) (avg-speed . 0.846) (timestamp . 1636650350) (total-elapsed-time . 29.562) (total-timer-time . 29.562) (message-index . 5) (total-strokes . 16) (total-calories . 8) (event . length) (event-type . stop) (avg-swimming-cadence . 32) (length-type . active) (max-heart-rate . 154) (avg-heart-rate . 150.1896551724138)) ((track ((start-time . 1636650187) (timestamp . 1636650187) (88 . 300) (heart-rate . 132)) ((start-time . 1636650188) (timestamp . 1636650188) (88 . 300) (heart-rate . 132)) ((start-time . 1636650189) (timestamp . 1636650189) (88 . 300) (heart-rate . 132)) ((start-time . 1636650190) (timestamp . 1636650190) (88 . 300) (heart-rate . 131)) ((start-time . 1636650191) (timestamp . 1636650191) (88 . 300) (heart-rate . 131)) ((start-time . 1636650192) (timestamp . 1636650192) (88 . 300) (heart-rate . 131)) ((start-time . 1636650193) (timestamp . 1636650193) (88 . 300) (heart-rate . 131)) ((start-time . 1636650194) (timestamp . 1636650194) (88 . 300) (heart-rate . 131)) ((start-time . 1636650195) (timestamp . 1636650195) (88 . 300) (heart-rate . 133)) ((start-time . 1636650196) (timestamp . 1636650196) (88 . 300) (heart-rate . 134)) ((start-time . 1636650197) (timestamp . 1636650197) (88 . 300) (heart-rate . 135)) ((start-time . 1636650198) (timestamp . 1636650198) (88 . 300) (heart-rate . 136)) ((start-time . 1636650199) (timestamp . 1636650199) (88 . 300) (heart-rate . 137)) ((start-time . 1636650200) (timestamp . 1636650200) (88 . 300) (heart-rate . 137)) ((start-time . 1636650201) (timestamp . 1636650201) (88 . 300) (heart-rate . 138)) ((start-time . 1636650202) (timestamp . 1636650202) (88 . 300) (heart-rate . 138)) ((start-time . 1636650203) (timestamp . 1636650203) (88 . 300) (heart-rate . 138)) ((start-time . 1636650204) (timestamp . 1636650204) (88 . 300) (heart-rate . 139)) ((start-time . 1636650205) (timestamp . 1636650205) (88 . 300) (heart-rate . 140)) ((start-time . 1636650206) (timestamp . 1636650206) (88 . 300) (heart-rate . 141)) ((start-time . 1636650207) (timestamp . 1636650207) (88 . 300) (heart-rate . 142)) ((start-time . 1636650208) (timestamp . 1636650208) (88 . 300) (heart-rate . 144))) (length-type . active) (start-time . 1636650186) (avg-cadence . 27) (total-cycles . 10) (avg-speed . 1.124) (timestamp . 1636650350) (total-elapsed-time . 22.25) (total-timer-time . 22.25) (message-index . 4) (total-strokes . 10) (total-calories . 5) (event . length) (event-type . stop) (avg-swimming-cadence . 27) (length-type . active) (max-heart-rate . 144) (avg-heart-rate . 135.4761904761905)) ((track ((start-time . 1636650164) (timestamp . 1636650164) (88 . 300) (heart-rate . 119)) ((start-time . 1636650165) (timestamp . 1636650165) (88 . 300) (heart-rate . 118)) ((start-time . 1636650166) (timestamp . 1636650166) (88 . 300) (heart-rate . 118)) ((start-time . 1636650167) (timestamp . 1636650167) (88 . 300) (heart-rate . 118)) ((start-time . 1636650168) (timestamp . 1636650168) (88 . 300) (heart-rate . 118)) ((start-time . 1636650169) (timestamp . 1636650169) (88 . 300) (heart-rate . 119)) ((start-time . 1636650170) (timestamp . 1636650170) (88 . 300) (heart-rate . 118)) ((start-time . 1636650171) (timestamp . 1636650171) (88 . 300) (heart-rate . 118)) ((start-time . 1636650172) (timestamp . 1636650172) (88 . 300) (heart-rate . 119)) ((start-time . 1636650173) (timestamp . 1636650173) (88 . 300) (heart-rate . 120)) ((start-time . 1636650174) (timestamp . 1636650174) (88 . 300) (heart-rate . 123)) ((start-time . 1636650175) (timestamp . 1636650175) (88 . 300) (heart-rate . 125)) ((start-time . 1636650176) (timestamp . 1636650176) (88 . 300) (heart-rate . 124)) ((start-time . 1636650177) (timestamp . 1636650177) (88 . 300) (heart-rate . 126)) ((start-time . 1636650178) (timestamp . 1636650178) (88 . 300) (heart-rate . 127)) ((start-time . 1636650179) (timestamp . 1636650179) (88 . 300) (heart-rate . 128)) ((start-time . 1636650180) (timestamp . 1636650180) (88 . 300) (heart-rate . 128)) ((start-time . 1636650181) (timestamp . 1636650181) (88 . 300) (heart-rate . 129)) ((start-time . 1636650182) (timestamp . 1636650182) (88 . 300) (heart-rate . 130)) ((start-time . 1636650183) (timestamp . 1636650183) (88 . 300) (heart-rate . 132)) ((start-time . 1636650184) (timestamp . 1636650184) (88 . 300) (heart-rate . 133)) ((start-time . 1636650185) (timestamp . 1636650185) (88 . 300) (heart-rate . 134)) ((start-time . 1636650186) (timestamp . 1636650186) (88 . 300) (heart-rate . 133))) (length-type . active) (start-time . 1636650163) (avg-cadence . 23) (total-cycles . 9) (avg-speed . 1.087) (timestamp . 1636650350) (total-elapsed-time . 23.0) (total-timer-time . 23.0) (message-index . 3) (total-strokes . 9) (total-calories . 5) (event . length) (event-type . stop) (avg-swimming-cadence . 23) (length-type . active) (max-heart-rate . 134) (avg-heart-rate . 124.13636363636363)) ((track ((start-time . 1636650123) (timestamp . 1636650123) (88 . 300) (heart-rate . 121)) ((start-time . 1636650124) (timestamp . 1636650124) (88 . 300) (heart-rate . 121)) ((start-time . 1636650125) (timestamp . 1636650125) (88 . 300) (heart-rate . 121)) ((start-time . 1636650126) (timestamp . 1636650126) (88 . 300) (heart-rate . 121)) ((start-time . 1636650127) (timestamp . 1636650127) (88 . 300) (heart-rate . 121)) ((start-time . 1636650128) (timestamp . 1636650128) (88 . 300) (heart-rate . 120)) ((start-time . 1636650129) (timestamp . 1636650129) (88 . 300) (heart-rate . 120)) ((start-time . 1636650130) (timestamp . 1636650130) (88 . 300) (heart-rate . 120)) ((start-time . 1636650131) (timestamp . 1636650131) (88 . 300) (heart-rate . 119)) ((start-time . 1636650132) (timestamp . 1636650132) (88 . 300) (heart-rate . 119)) ((start-time . 1636650133) (timestamp . 1636650133) (88 . 300) (heart-rate . 119)) ((start-time . 1636650134) (timestamp . 1636650134) (88 . 300) (heart-rate . 119)) ((start-time . 1636650135) (timestamp . 1636650135) (88 . 300) (heart-rate . 119)) ((start-time . 1636650136) (timestamp . 1636650136) (88 . 300) (heart-rate . 120)) ((start-time . 1636650137) (timestamp . 1636650137) (88 . 300) (heart-rate . 120)) ((start-time . 1636650138) (timestamp . 1636650138) (88 . 300) (heart-rate . 120)) ((start-time . 1636650139) (timestamp . 1636650139) (88 . 300) (heart-rate . 119)) ((start-time . 1636650140) (timestamp . 1636650140) (88 . 300) (heart-rate . 119)) ((start-time . 1636650141) (timestamp . 1636650141) (88 . 300) (heart-rate . 119)) ((start-time . 1636650142) (timestamp . 1636650142) (88 . 300) (heart-rate . 119)) ((start-time . 1636650143) (timestamp . 1636650143) (88 . 300) (heart-rate . 119)) ((start-time . 1636650144) (timestamp . 1636650144) (88 . 300) (heart-rate . 119)) ((start-time . 1636650145) (timestamp . 1636650145) (88 . 300) (heart-rate . 118)) ((start-time . 1636650146) (timestamp . 1636650146) (88 . 300) (heart-rate . 118)) ((start-time . 1636650147) (timestamp . 1636650147) (88 . 300) (heart-rate . 119)) ((start-time . 1636650148) (timestamp . 1636650148) (88 . 300) (heart-rate . 120)) ((start-time . 1636650149) (timestamp . 1636650149) (88 . 300) (heart-rate . 121)) ((start-time . 1636650150) (timestamp . 1636650150) (88 . 300) (heart-rate . 121)) ((start-time . 1636650151) (timestamp . 1636650151) (88 . 300) (heart-rate . 122)) ((start-time . 1636650152) (timestamp . 1636650152) (88 . 300) (heart-rate . 122)) ((start-time . 1636650153) (timestamp . 1636650153) (88 . 300) (heart-rate . 121)) ((start-time . 1636650154) (timestamp . 1636650154) (88 . 300) (heart-rate . 121)) ((start-time . 1636650155) (timestamp . 1636650155) (88 . 300) (heart-rate . 122)) ((start-time . 1636650156) (timestamp . 1636650156) (88 . 300) (heart-rate . 122)) ((start-time . 1636650157) (timestamp . 1636650157) (88 . 300) (heart-rate . 122)) ((start-time . 1636650158) (timestamp . 1636650158) (88 . 300) (heart-rate . 122)) ((start-time . 1636650159) (timestamp . 1636650159) (88 . 300) (heart-rate . 121)) ((start-time . 1636650160) (timestamp . 1636650160) (88 . 300) (heart-rate . 120)) ((start-time . 1636650161) (timestamp . 1636650161) (88 . 300) (heart-rate . 119)) ((start-time . 1636650162) (timestamp . 1636650162) (88 . 300) (heart-rate . 119)) ((start-time . 1636650163) (timestamp . 1636650163) (88 . 300) (heart-rate . 119))) (length-type . active) (start-time . 1636650123) (avg-cadence . 22) (total-cycles . 15) (avg-speed . 0.614) (timestamp . 1636650350) (total-elapsed-time . 40.687) (total-timer-time . 40.687) (message-index . 2) (total-strokes . 15) (total-calories . 5) (event . length) (event-type . stop) (avg-swimming-cadence . 22) (length-type . active) (max-heart-rate . 122) (avg-heart-rate . 120.07500000000002)) ((track ((start-time . 1636650103) (timestamp . 1636650103) (88 . 300) (heart-rate . 106)) ((start-time . 1636650104) (timestamp . 1636650104) (88 . 300) (heart-rate . 108)) ((start-time . 1636650105) (timestamp . 1636650105) (88 . 300) (heart-rate . 110)) ((start-time . 1636650106) (timestamp . 1636650106) (88 . 300) (heart-rate . 111)) ((start-time . 1636650107) (timestamp . 1636650107) (88 . 300) (heart-rate . 111)) ((start-time . 1636650108) (timestamp . 1636650108) (88 . 300) (heart-rate . 112)) ((start-time . 1636650109) (timestamp . 1636650109) (88 . 300) (heart-rate . 111)) ((start-time . 1636650110) (timestamp . 1636650110) (88 . 300) (heart-rate . 112)) ((start-time . 1636650111) (timestamp . 1636650111) (88 . 300) (heart-rate . 112)) ((start-time . 1636650112) (timestamp . 1636650112) (88 . 300) (heart-rate . 113)) ((start-time . 1636650113) (timestamp . 1636650113) (88 . 300) (heart-rate . 114)) ((start-time . 1636650114) (timestamp . 1636650114) (88 . 300) (heart-rate . 115)) ((start-time . 1636650115) (timestamp . 1636650115) (88 . 300) (heart-rate . 116)) ((start-time . 1636650116) (timestamp . 1636650116) (88 . 300) (heart-rate . 116)) ((start-time . 1636650117) (timestamp . 1636650117) (88 . 300) (heart-rate . 117)) ((start-time . 1636650118) (timestamp . 1636650118) (88 . 300) (heart-rate . 118)) ((start-time . 1636650119) (timestamp . 1636650119) (88 . 300) (heart-rate . 120)) ((start-time . 1636650120) (timestamp . 1636650120) (88 . 300) (heart-rate . 121)) ((start-time . 1636650121) (timestamp . 1636650121) (88 . 300) (heart-rate . 123)) ((start-time . 1636650122) (timestamp . 1636650122) (88 . 300) (heart-rate . 122))) (length-type . active) (start-time . 1636650102) (avg-cadence . 26) (total-cycles . 9) (avg-speed . 1.223) (timestamp . 1636650350) (total-elapsed-time . 20.437) (total-timer-time . 20.437) (message-index . 1) (total-strokes . 9) (total-calories . 8) (event . length) (event-type . stop) (avg-swimming-cadence . 26) (length-type . active) (max-heart-rate . 123) (avg-heart-rate . 114.42105263157896)) ((track ((start-time . 1636650078) (timestamp . 1636650078) (88 . 100) (heart-rate . 77)) ((start-time . 1636650079) (timestamp . 1636650079) (88 . 100) (heart-rate . 79)) ((start-time . 1636650080) (timestamp . 1636650080) (88 . 100) (heart-rate . 78)) ((start-time . 1636650081) (timestamp . 1636650081) (88 . 100) (heart-rate . 79)) ((start-time . 1636650082) (timestamp . 1636650082) (88 . 100) (heart-rate . 79)) ((start-time . 1636650083) (timestamp . 1636650083) (88 . 100) (heart-rate . 79)) ((start-time . 1636650084) (timestamp . 1636650084) (88 . 300) (heart-rate . 82)) ((start-time . 1636650085) (timestamp . 1636650085) (88 . 300) (heart-rate . 83)) ((start-time . 1636650086) (timestamp . 1636650086) (88 . 300) (heart-rate . 84)) ((start-time . 1636650087) (timestamp . 1636650087) (88 . 300) (heart-rate . 85)) ((start-time . 1636650088) (timestamp . 1636650088) (88 . 300) (heart-rate . 85)) ((start-time . 1636650089) (timestamp . 1636650089) (88 . 300) (heart-rate . 86)) ((start-time . 1636650090) (timestamp . 1636650090) (88 . 300) (heart-rate . 87)) ((start-time . 1636650091) (timestamp . 1636650091) (88 . 300) (heart-rate . 88)) ((start-time . 1636650092) (timestamp . 1636650092) (88 . 300) (heart-rate . 88)) ((start-time . 1636650093) (timestamp . 1636650093) (88 . 300) (heart-rate . 90)) ((start-time . 1636650094) (timestamp . 1636650094) (88 . 300) (heart-rate . 91)) ((start-time . 1636650095) (timestamp . 1636650095) (88 . 300) (heart-rate . 92)) ((start-time . 1636650096) (timestamp . 1636650096) (88 . 300) (heart-rate . 93)) ((start-time . 1636650097) (timestamp . 1636650097) (88 . 300) (heart-rate . 94)) ((start-time . 1636650098) (timestamp . 1636650098) (88 . 300) (heart-rate . 96)) ((start-time . 1636650099) (timestamp . 1636650099) (88 . 300) (heart-rate . 98)) ((start-time . 1636650100) (timestamp . 1636650100) (88 . 300) (heart-rate . 100)) ((start-time . 1636650101) (timestamp . 1636650101) (88 . 300) (heart-rate . 102)) ((start-time . 1636650102) (timestamp . 1636650102) (88 . 300) (heart-rate . 104))) (length-type . active) (start-time . 1636650077) (avg-cadence . 21) (total-cycles . 9) (avg-speed . 0.99) (timestamp . 1636650350) (total-elapsed-time . 25.25) (total-timer-time . 25.25) (message-index . 0) (total-strokes . 9) (total-calories . 5) (event . length) (event-type . stop) (avg-swimming-cadence . 21) (length-type . active) (max-heart-rate . 104) (avg-heart-rate . 87.85416666666666)))"
2021-11-19 16:39:02.297 fit-file: remaining records after processing LENGTHS
 ### DEBUGGER: "lengths = (((length-type . idle) (start-time . 1636650413) (timestamp . 1636650481) (total-elapsed-time . 55.875) (total-timer-time . 55.875) (message-index . 15) (event . length) (event-type . stop) (length-type . idle)))"

Does this problem happen with the file you attached to this issue? (see the first comment), if not, can you please attach a FIT file with this problem?


As a side note, different devices, and sometimes different firmware versions record FIT files differently. Some differences I noticed in the past:

  • sometimes LAP records are recorded when they happen, sometimes they are at the end of the activity all together, like you showed
  • sometimes "timestamp" is the timestamp when the event, like a lap, occurred, but sometimes it is the time when the event was written to the fit file

This is why the FIT file reader is so complex when it associates track points with lengths and laps, and it looks like we found another special case....

No, it happens on another file: 2021-11-11-18-01-18.fit.zip

I pushed a fix for this here: https://github.com/alex-hhh/ActivityLog2/tree/ah/pr74-a, can you please try it out?

The problem was that the length and lap message was written into the FIT file several seconds after the next lap started, so the reader would discard these track points. I reworked the reader to work with this case as well, but it took quite a bit of time to do it...

Thanks! Looks good, but I noticed that at the end of the session one extra lap is added without any length.
grafik

I noticed the same thing, however, in the FIT file you attached earlier, the last "length" covers the time between 1636653565 and 1636653602.188 (since it has a duration of 37.188 seconds). The extra record has a start time of 1636653603, so it is outside the last length. Where should we put this record?

Also, when the battery runs out, the FIT files are incomplete, and several records at the end are not associated with a lap. The FIT file reader has to create an additional lap to hold these records. Or should they be discarded?

I noticed the same thing, however, in the FIT file you attached earlier, the last "length" covers the time between 1636653565 and 1636653602.188 (since it has a duration of 37.188 seconds). The extra record has a start time of 1636653603, so it is outside the last length. Where should we put this record?

it would be great if we could interpolate the values at the end time (1636653602.188). (However in lap swimming it is only hr data and they are not really exact, so i am fine with discarding too.) Where was a record with a start time of 1636653603 stored before this change?

Also, when the battery runs out, the FIT files are incomplete, and several records at the end are not associated with a lap. The FIT file reader has to create an additional lap to hold these records. Or should they be discarded?

In lap swimming it could be only HR data. IMHO HR data without assignment to specific length is useless, so it's fine to discard it.

I noticed the same thing, however, in the FIT file you attached earlier, the last "length" covers the time between 1636653565 and 1636653602.188 (since it has a duration of 37.188 seconds). The extra record has a start time of 1636653603, so it is outside the last length. Where should we put this record?

it would be great if we could interpolate the values at the end time (1636653602.188). (However in lap swimming it is only hr data and they are not really exact, so i am fine with discarding too.) Where was a record with a start time of 1636653603 stored before this change?

Before this change, each time a length message was seen, it would associate the trackpoints for that record (based on time) and discard the rest of the trackpoints, so this record would have been lost along with the other records you reported missing when reopening this issue...

Also, when the battery runs out, the FIT files are incomplete, and several records at the end are not associated with a lap. The FIT file reader has to create an additional lap to hold these records. Or should they be discarded?

In lap swimming it could be only HR data. IMHO HR data without assignment to specific length is useless, so it's fine to discard it.

I pushed a change to discard trackpoints after the last lap, but only for a single trackpoint. I will need to verify this a bit more...


The fit reader needs to read FIT files for other sports as well, not just lap swimming. It also needs to read FIT files generated by different devices. Unfortunately, the FIT standard is ambiguous on how trackpoints are grouped in lengths and laps and also various devices write things differently, so making a reader that can handle all the cases is difficult... I could easily fix the fit reader to read lap swimming files correctly, but that would break other cases....

I have updated today to this commit b9ca5ba. There was one anomaly visible on the hr graph of today's training.
image
The autopause function made an mistake at that momemt. It started pause too early. So i pressed a manual pause at the lap end.
image

I am ok with this. I thought it might interest you.

The fit file reader in ActivityLog2 (AL2) will read the FIT file as it was recorded, because it is difficult to determine what the user intentions were. The AL2 application has some limited features to fix recording mistakes, like the Swim Lap editor, the Power Spikes tool, and the FIT file reader tries to recover FIT files which were recorded while the battery run out, but this is about it. I could write more tools, but this takes time...

For other fit file corrections, there are some other applications which fix more problems, two that I am aware of are the Fit File Tools and the Fit File Repair Tool -- the last one cost money.

Also, the vertical line is how AL2 represents a timer stop in recording -- I wanted to make them very visible, but perhaps I could change the display so that they just show up as a gap in the plot, which would make less of an impact...

With regards to the extra records at the end, I decided to keep them and have the AL2 create an extra lap at the end. This is because:

  • the records are actually present in the FIT file
  • they are recorded while the timer is running (i.e. before a "Timer Stop" message is recorded)
  • from a timestamp point of view, they are not in the previous lap, so I cannot add them there (the code would also have to work if a record is present, for example 10 minutes after the end of the lap)
  • if these records are discarded, this breaks reading other fit files and I could not find an algorithm that works correctly for all the files that I have...

If you want, you can set remaining-records to null here, this will discard them, but this is not a general fix:

(define remaining-records

I will also close this issue, as the problem reported is fixed. If you find additional problems can you please create separate issues, as it is easier to track the fixes? Thanks.