From 5ae5066768d5e71b27dade9fcd5017242e4ea5f8 Mon Sep 17 00:00:00 2001 From: Soeren Kroell Date: Mon, 26 Mar 2018 10:21:57 +0200 Subject: [PATCH 01/10] [TASK] Test import.py added --- import.py | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++ run_tests.py | 4 ++-- 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 import.py diff --git a/import.py b/import.py new file mode 100644 index 0000000..6bc06d8 --- /dev/null +++ b/import.py @@ -0,0 +1,53 @@ +#!/usr/bin/python + +from fitparse import FitFile +import sys +import datetime + +now = datetime.datetime.now() + +# if(len(sys.argv)){ +# fileName = sys.argv[0]; +# } else{ +# fileName = '/Users/soerenkroell/Desktop/19016651587_mo_swim_aber_ohne_session.fit'; +# } + + +if len(sys.argv) >= 2: + fileName = sys.argv[1] +else: + fileName = '/Users/soerenkroell/Desktop/19016651587_mo_swim_aber_ohne_session.fit' + +print +print "Start " + now.strftime("%H:%M:%S") +print + +print "importing file %s"%(fileName) + +if len(sys.argv) >= 3: + fieldName = sys.argv[2] +else: + fieldName = 'session' + +fitfile = FitFile(fileName) + +# Get all data messages that are of type record +for record in fitfile.get_messages(fieldName): + + # Go through all the data entries in this record + for record_data in record: + + # Print the records name and value (and units if it has any) + if record_data.units: + print " * %s: %s %s" % ( + record_data.name, record_data.value, record_data.units, + ) + else: + print " * %s: %s" % (record_data.name, record_data.value) + print + +now = datetime.datetime.now() + +print +print"End " + now.strftime("%H:%M:%S") +print diff --git a/run_tests.py b/run_tests.py index 3670d1a..4fdbbdc 100755 --- a/run_tests.py +++ b/run_tests.py @@ -5,8 +5,8 @@ if sys.version_info >= (2, 7): import unittest -else: - import unittest2 as unittest +# else: +# import unittest2 as unittest TEST_PATH = os.path.join(os.path.realpath(os.path.dirname(__file__)), 'tests') From f383d8c0f7cf0ad70555f7b7b86e84fb6368d4f3 Mon Sep 17 00:00:00 2001 From: Soeren Kroell Date: Mon, 26 Mar 2018 10:24:49 +0200 Subject: [PATCH 02/10] [TASK] Test import.py updated --- import.py | 57 +++++++++++++++++++++++++------------------------------ 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/import.py b/import.py index 6bc06d8..5bccdaf 100644 --- a/import.py +++ b/import.py @@ -6,48 +6,43 @@ now = datetime.datetime.now() -# if(len(sys.argv)){ -# fileName = sys.argv[0]; -# } else{ -# fileName = '/Users/soerenkroell/Desktop/19016651587_mo_swim_aber_ohne_session.fit'; -# } - +fileName = False if len(sys.argv) >= 2: fileName = sys.argv[1] -else: - fileName = '/Users/soerenkroell/Desktop/19016651587_mo_swim_aber_ohne_session.fit' - -print -print "Start " + now.strftime("%H:%M:%S") -print - -print "importing file %s"%(fileName) if len(sys.argv) >= 3: fieldName = sys.argv[2] else: fieldName = 'session' -fitfile = FitFile(fileName) +if fileName: + print "importing file %s" % (fileName) + print + print "Start " + now.strftime("%H:%M:%S") + print -# Get all data messages that are of type record -for record in fitfile.get_messages(fieldName): + fitfile = FitFile(fileName) - # Go through all the data entries in this record - for record_data in record: + # Get all data messages that are of type record + for record in fitfile.get_messages(fieldName): - # Print the records name and value (and units if it has any) - if record_data.units: - print " * %s: %s %s" % ( - record_data.name, record_data.value, record_data.units, - ) - else: - print " * %s: %s" % (record_data.name, record_data.value) - print + # Go through all the data entries in this record + for record_data in record: -now = datetime.datetime.now() + # Print the records name and value (and units if it has any) + if record_data.units: + print " * %s: %s %s" % ( + record_data.name, record_data.value, record_data.units, + ) + else: + print " * %s: %s" % (record_data.name, record_data.value) + print + + now = datetime.datetime.now() -print -print"End " + now.strftime("%H:%M:%S") -print + print + print "End " + now.strftime("%H:%M:%S") + print +else: + print "Missing filname" From 45c1b04af916310e5a888764d822bb639ce91b66 Mon Sep 17 00:00:00 2001 From: Soeren Kroell Date: Mon, 26 Mar 2018 10:43:33 +0200 Subject: [PATCH 03/10] [TASK] Test import.py updated --- import.py | 72 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/import.py b/import.py index 5bccdaf..cb03ee9 100644 --- a/import.py +++ b/import.py @@ -1,48 +1,56 @@ #!/usr/bin/python from fitparse import FitFile + import sys import datetime +import os +import json -now = datetime.datetime.now() - -fileName = False +def importFit(fileName, fieldName): + now = datetime.datetime.now() -if len(sys.argv) >= 2: - fileName = sys.argv[1] + if fileName: + print "importing file %s" % (fileName) + print + print "Start " + now.strftime("%H:%M:%S") + print -if len(sys.argv) >= 3: - fieldName = sys.argv[2] -else: - fieldName = 'session' + fitfile = FitFile(fileName) -if fileName: - print "importing file %s" % (fileName) - print - print "Start " + now.strftime("%H:%M:%S") - print + # Get all data messages that are of type record + for record in fitfile.get_messages(fieldName): - fitfile = FitFile(fileName) + # Go through all the data entries in this record + for record_data in record: - # Get all data messages that are of type record - for record in fitfile.get_messages(fieldName): + # Print the records name and value (and units if it has any) + if record_data.units: + print " * %s: %s %s" % ( + record_data.name, record_data.value, record_data.units, + ) + else: + print " * %s: %s" % (record_data.name, record_data.value) + print - # Go through all the data entries in this record - for record_data in record: + now = datetime.datetime.now() - # Print the records name and value (and units if it has any) - if record_data.units: - print " * %s: %s %s" % ( - record_data.name, record_data.value, record_data.units, - ) - else: - print " * %s: %s" % (record_data.name, record_data.value) print + print "End " + now.strftime("%H:%M:%S") + print + else: + print "Missing filname" - now = datetime.datetime.now() - print - print "End " + now.strftime("%H:%M:%S") - print -else: - print "Missing filname" +if __name__ == "__main__": + fileName = False + + if len(sys.argv) >= 2: + fileName = sys.argv[1] + + if len(sys.argv) >= 3: + fieldName = sys.argv[2] + else: + fieldName = 'session' + + print importFit(fileName, fieldName) From 0e4ce6f494e555ec076b3ea5ba971e8c027be6b2 Mon Sep 17 00:00:00 2001 From: Soeren Kroell Date: Mon, 26 Mar 2018 11:50:51 +0200 Subject: [PATCH 04/10] [TASK] Test import.py updated --- import.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/import.py b/import.py index cb03ee9..8dfbb6b 100644 --- a/import.py +++ b/import.py @@ -5,7 +5,12 @@ import sys import datetime import os -import json +# import json +import simplejson as json + +def importFitAndReturnJson(fileName, fieldName): + fitfile = FitFile(fileName) + return json.dumps(fitfile.get_messages(fieldName, False, True), iterable_as_array=True) def importFit(fileName, fieldName): now = datetime.datetime.now() @@ -53,4 +58,12 @@ def importFit(fileName, fieldName): else: fieldName = 'session' - print importFit(fileName, fieldName) + if len(sys.argv) >= 4: + asJson = sys.argv[3] + else: + asJson = False + + if asJson: + print importFitAndReturnJson(fileName, fieldName) + else: + print importFit(fileName, fieldName) From 60130be90bbb59c991d577573532485ac839c832 Mon Sep 17 00:00:00 2001 From: Soeren Kroell Date: Mon, 26 Mar 2018 12:03:26 +0200 Subject: [PATCH 05/10] [TASK] Test import.py updated --- import.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/import.py b/import.py index 8dfbb6b..2fc9baa 100644 --- a/import.py +++ b/import.py @@ -8,9 +8,18 @@ # import json import simplejson as json +from datetime import date + +def json_serial(obj): + """JSON serializer for objects not serializable by default json code""" + + if isinstance(obj, (datetime, date)): + return obj.isoformat() + raise TypeError("Type %s not serializable" % type(obj)) + def importFitAndReturnJson(fileName, fieldName): fitfile = FitFile(fileName) - return json.dumps(fitfile.get_messages(fieldName, False, True), iterable_as_array=True) + return json.dumps(fitfile.get_messages(fieldName, False, True), indent=4, sort_keys=True, default=str, iterable_as_array=True) def importFit(fileName, fieldName): now = datetime.datetime.now() From f2fc589eaf0480f908f62121b94de2f26269e908 Mon Sep 17 00:00:00 2001 From: Soeren Kroell Date: Mon, 26 Mar 2018 12:06:41 +0200 Subject: [PATCH 06/10] [TASK] Test import.py updated --- import.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/import.py b/import.py index 2fc9baa..169f2b9 100644 --- a/import.py +++ b/import.py @@ -10,16 +10,9 @@ from datetime import date -def json_serial(obj): - """JSON serializer for objects not serializable by default json code""" - - if isinstance(obj, (datetime, date)): - return obj.isoformat() - raise TypeError("Type %s not serializable" % type(obj)) - def importFitAndReturnJson(fileName, fieldName): fitfile = FitFile(fileName) - return json.dumps(fitfile.get_messages(fieldName, False, True), indent=4, sort_keys=True, default=str, iterable_as_array=True) + return json.dumps(fitfile.get_messages(fieldName, False, True), sort_keys=True, default=str, iterable_as_array=True) def importFit(fileName, fieldName): now = datetime.datetime.now() From 494376617bb9295d7937ca2955d848e067ffa90d Mon Sep 17 00:00:00 2001 From: Soeren Kroell Date: Mon, 26 Mar 2018 12:53:30 +0200 Subject: [PATCH 07/10] =?UTF-8?q?[TASK]=20Anpassungen=20f=C3=BCr=20Python3?= =?UTF-8?q?=20in=20import.py=20+=20get=5Fmessages=20all=20m=C3=B6glich?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- import.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/import.py b/import.py index 169f2b9..0f71a1b 100644 --- a/import.py +++ b/import.py @@ -5,7 +5,6 @@ import sys import datetime import os -# import json import simplejson as json from datetime import date @@ -18,9 +17,9 @@ def importFit(fileName, fieldName): now = datetime.datetime.now() if fileName: - print "importing file %s" % (fileName) + print ("importing file %s" % (fileName)) print - print "Start " + now.strftime("%H:%M:%S") + print ("Start " + now.strftime("%H:%M:%S")) print fitfile = FitFile(fileName) @@ -33,20 +32,20 @@ def importFit(fileName, fieldName): # Print the records name and value (and units if it has any) if record_data.units: - print " * %s: %s %s" % ( + print (" * %s: %s %s" % ( record_data.name, record_data.value, record_data.units, - ) + )) else: - print " * %s: %s" % (record_data.name, record_data.value) + print (" * %s: %s" % (record_data.name, record_data.value)) print now = datetime.datetime.now() print - print "End " + now.strftime("%H:%M:%S") + print ("End " + now.strftime("%H:%M:%S")) print else: - print "Missing filname" + print ("Missing filname") if __name__ == "__main__": @@ -57,15 +56,17 @@ def importFit(fileName, fieldName): if len(sys.argv) >= 3: fieldName = sys.argv[2] + if fieldName == 'all': + fieldName = None; else: - fieldName = 'session' + fieldName = None if len(sys.argv) >= 4: asJson = sys.argv[3] else: asJson = False - + if asJson: - print importFitAndReturnJson(fileName, fieldName) + print (importFitAndReturnJson(fileName, fieldName)) else: - print importFit(fileName, fieldName) + print (importFit(fileName, fieldName)) From 4f8b1a9a4d2df72ae050c3118a7f9e8886f139c4 Mon Sep 17 00:00:00 2001 From: Soeren Kroell Date: Tue, 18 Dec 2018 09:54:08 +0100 Subject: [PATCH 08/10] [TASK] Composer manifest --- .gitignore | 2 ++ composer.json | 11 +++++++++++ 2 files changed, 13 insertions(+) create mode 100644 composer.json diff --git a/.gitignore b/.gitignore index 749a4c4..8e17f12 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,5 @@ target/ # python-fitparse specific FitSDK* + +/vendor/ diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b4d8928 --- /dev/null +++ b/composer.json @@ -0,0 +1,11 @@ +{ + "name": "time2tri/parser-fit", + "description": "Python FIT Parser", + "authors": [ + { + "name": "Soeren Kroell", + "email": "soeren@time2tri.me" + } + ], + "require": {} +} From 367f6d5281818e64d59b0e0ac3a8f8164712110e Mon Sep 17 00:00:00 2001 From: Soeren Kroell Date: Sat, 9 Feb 2019 23:13:14 +0100 Subject: [PATCH 09/10] Update README.md --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index b221c59..ccc94b0 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,14 @@ Install from [![PyPI](https://img.shields.io/pypi/v/fitparse.svg)](https://pypi. pip install fitparse ``` +Dependencies +------------ +``` +curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py" +python get-pip.py +pip install simplejson +``` + FIT files ------------ - FIT files contain data stored in a binary file format. From 36c5594e4239a1ddd14697a91353c8b251e27c43 Mon Sep 17 00:00:00 2001 From: Soeren Kroell Date: Sat, 18 Apr 2020 14:15:13 +0200 Subject: [PATCH 10/10] [TASK] Update + SDK auf 21.27.00 --- .gitignore | 2 + README.md | 9 + etc/requirements-test.in | 2 + fitparse/__init__.py | 2 +- fitparse/base.py | 10 +- fitparse/profile.py | 2642 +++++++++++++++++++++++++++++++---- fitparse/records.py | 35 +- fitparse/utils.py | 5 +- requirements-test.txt | 12 + scripts/generate_profile.py | 136 +- 10 files changed, 2562 insertions(+), 293 deletions(-) create mode 100644 etc/requirements-test.in create mode 100644 requirements-test.txt diff --git a/.gitignore b/.gitignore index 8e17f12..9d39e57 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,5 @@ target/ FitSDK* /vendor/ +_fitparse +_scripts diff --git a/README.md b/README.md index ccc94b0..baa9d1d 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,15 @@ python get-pip.py pip install simplejson ``` +Problems +------------ +If the error `There is no item named 'c/fit.h' in the archive"` appears: +``` +Replace two files names in scripts/generate_profile.py may help. +'c/fit.h' to 'FitSDKRelease_xx.xx.00/c/fit.h' +'Profile.xls' to 'FitSDKRelease_xx.xx.00/Profile.xls' +``` + FIT files ------------ - FIT files contain data stored in a binary file format. diff --git a/etc/requirements-test.in b/etc/requirements-test.in new file mode 100644 index 0000000..5c90533 --- /dev/null +++ b/etc/requirements-test.in @@ -0,0 +1,2 @@ +pip-tools +coverage diff --git a/fitparse/__init__.py b/fitparse/__init__.py index 1bbc49e..9053f66 100644 --- a/fitparse/__init__.py +++ b/fitparse/__init__.py @@ -3,7 +3,7 @@ from fitparse.processors import FitFileDataProcessor, StandardUnitsDataProcessor -__version__ = '1.0.1' +__version__ = '1.1.0' __all__ = [ 'FitFileDataProcessor', 'FitFile', 'FitParseError', 'StandardUnitsDataProcessor', 'DataMessage' diff --git a/fitparse/base.py b/fitparse/base.py index 125d620..751f94a 100644 --- a/fitparse/base.py +++ b/fitparse/base.py @@ -19,7 +19,7 @@ class FitFile(object): - def __init__(self, fileish, check_crc=True, data_processor=None): + def __init__(self, fileish, check_crc=False, data_processor=None): self._file = fileish_open(fileish, 'rb') self.check_crc = check_crc @@ -30,6 +30,7 @@ def __init__(self, fileish, check_crc=True, data_processor=None): self._file.seek(0, os.SEEK_END) self._filesize = self._file.tell() self._file.seek(0, os.SEEK_SET) + self._messages = [] # Start off by parsing the file header (sets initial attribute values) self._parse_file_header() @@ -98,8 +99,8 @@ def _parse_file_header(self): self._compressed_ts_accumulator = 0 self._crc = Crc() self._local_mesgs = {} - self._messages = [] + # Every FIT must have 'FIT' string in the 8th byte. So, it seems your FIT is wrong. header_data = self._read(12) if header_data[8:12] != b'.FIT': raise FitHeaderError("Invalid .FIT File Header") @@ -319,7 +320,10 @@ def _parse_data_message(self, header): if field.components: for component in field.components: # Render its raw value - cmp_raw_value = component.render(raw_value) + try: + cmp_raw_value = component.render(raw_value) + except ValueError: + continue # Apply accumulated value if component.accumulate and cmp_raw_value is not None: diff --git a/fitparse/profile.py b/fitparse/profile.py index 3e59617..aa6ddbd 100644 --- a/fitparse/profile.py +++ b/fitparse/profile.py @@ -1,7 +1,8 @@ -################# BEGIN AUTOMATICALLY GENERATED FIT PROFILE ################## -########################### DO NOT EDIT THIS FILE ############################ -####### EXPORTED PROFILE FROM SDK VERSION 20.33 AT 2017-05-17 22:36:12 ####### -########## PARSED 118 TYPES (1699 VALUES), 76 MESSAGES (950 FIELDS) ########## + +# ***************** BEGIN AUTOMATICALLY GENERATED FIT PROFILE ****************** +# *************************** DO NOT EDIT THIS FILE **************************** +# ************ EXPORTED PROFILE FROM SDK VERSION 20.8 ON 2019-03-05 ************ +# ********* PARSED 161 TYPES (2985 VALUES), 85 MESSAGES (1038 FIELDS) ********** from fitparse.records import ( ComponentField, @@ -14,6 +15,9 @@ ) +FIELD_NUM_TIMESTAMP = 253 + + FIELD_TYPES = { 'activity': FieldType( name='activity', @@ -219,6 +223,13 @@ 6: 'key_and_messages_and_smart_notifications', }, ), + 'backlight_timeout': FieldType( # Timeout in seconds. + name='backlight_timeout', + base_type=BASE_TYPES[0x02], # uint8 + values={ + 0: 'infinite', # Backlight stays on forever. + }, + ), 'battery_status': FieldType( name='battery_status', base_type=BASE_TYPES[0x02], # uint8 @@ -232,6 +243,39 @@ 7: 'unknown', }, ), + 'bench_press_exercise_name': FieldType( + name='bench_press_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'alternating_dumbbell_chest_press_on_swiss_ball', + 1: 'barbell_bench_press', + 2: 'barbell_board_bench_press', + 3: 'barbell_floor_press', + 4: 'close_grip_barbell_bench_press', + 5: 'decline_dumbbell_bench_press', + 6: 'dumbbell_bench_press', + 7: 'dumbbell_floor_press', + 8: 'incline_barbell_bench_press', + 9: 'incline_dumbbell_bench_press', + 10: 'incline_smith_machine_bench_press', + 11: 'isometric_barbell_bench_press', + 12: 'kettlebell_chest_press', + 13: 'neutral_grip_dumbbell_bench_press', + 14: 'neutral_grip_dumbbell_incline_bench_press', + 15: 'one_arm_floor_press', + 16: 'weighted_one_arm_floor_press', + 17: 'partial_lockout', + 18: 'reverse_grip_barbell_bench_press', + 19: 'reverse_grip_incline_bench_press', + 20: 'single_arm_cable_chest_press', + 21: 'single_arm_dumbbell_bench_press', + 22: 'smith_machine_bench_press', + 23: 'swiss_ball_dumbbell_chest_press', + 24: 'triple_stop_barbell_bench_press', + 25: 'wide_grip_barbell_bench_press', + 26: 'alternating_dumbbell_chest_press', + }, + ), 'bike_light_beam_angle_mode': FieldType( name='bike_light_beam_angle_mode', base_type=BASE_TYPES[0x02], # uint8 @@ -311,6 +355,33 @@ 4: 'error_irregular_heart_rate', }, ), + 'calf_raise_exercise_name': FieldType( + name='calf_raise_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: '3_way_calf_raise', + 1: '3_way_weighted_calf_raise', + 2: '3_way_single_leg_calf_raise', + 3: '3_way_weighted_single_leg_calf_raise', + 4: 'donkey_calf_raise', + 5: 'weighted_donkey_calf_raise', + 6: 'seated_calf_raise', + 7: 'weighted_seated_calf_raise', + 8: 'seated_dumbbell_toe_raise', + 9: 'single_leg_bent_knee_calf_raise', + 10: 'weighted_single_leg_bent_knee_calf_raise', + 11: 'single_leg_decline_push_up', + 12: 'single_leg_donkey_calf_raise', + 13: 'weighted_single_leg_donkey_calf_raise', + 14: 'single_leg_hip_raise_with_knee_hold', + 15: 'single_leg_standing_calf_raise', + 16: 'single_leg_standing_dumbbell_calf_raise', + 17: 'standing_barbell_calf_raise', + 18: 'standing_calf_raise', + 19: 'weighted_standing_calf_raise', + 20: 'standing_dumbbell_calf_raise', + }, + ), 'camera_event_type': FieldType( name='camera_event_type', base_type=BASE_TYPES[0x00], # enum @@ -340,6 +411,45 @@ 3: 'camera_orientation_270', }, ), + 'cardio_exercise_name': FieldType( + name='cardio_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'bob_and_weave_circle', + 1: 'weighted_bob_and_weave_circle', + 2: 'cardio_core_crawl', + 3: 'weighted_cardio_core_crawl', + 4: 'double_under', + 5: 'weighted_double_under', + 6: 'jump_rope', + 7: 'weighted_jump_rope', + 8: 'jump_rope_crossover', + 9: 'weighted_jump_rope_crossover', + 10: 'jump_rope_jog', + 11: 'weighted_jump_rope_jog', + 12: 'jumping_jacks', + 13: 'weighted_jumping_jacks', + 14: 'ski_moguls', + 15: 'weighted_ski_moguls', + 16: 'split_jacks', + 17: 'weighted_split_jacks', + 18: 'squat_jacks', + 19: 'weighted_squat_jacks', + 20: 'triple_under', + 21: 'weighted_triple_under', + }, + ), + 'carry_exercise_name': FieldType( + name='carry_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'bar_holds', + 1: 'farmers_walk', + 2: 'farmers_walk_on_toes', + 3: 'hex_dumbbell_hold', + 4: 'overhead_carry', + }, + ), 'checksum': FieldType( name='checksum', base_type=BASE_TYPES[0x02], # uint8 @@ -348,6 +458,35 @@ 1: 'ok', # Set to mark checksum as valid if computes to invalid values 0 or 0xFF. Checksum can also be set to ok to save encoding computation time. }, ), + 'chop_exercise_name': FieldType( + name='chop_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'cable_pull_through', + 1: 'cable_rotational_lift', + 2: 'cable_woodchop', + 3: 'cross_chop_to_knee', + 4: 'weighted_cross_chop_to_knee', + 5: 'dumbbell_chop', + 6: 'half_kneeling_rotation', + 7: 'weighted_half_kneeling_rotation', + 8: 'half_kneeling_rotational_chop', + 9: 'half_kneeling_rotational_reverse_chop', + 10: 'half_kneeling_stability_chop', + 11: 'half_kneeling_stability_reverse_chop', + 12: 'kneeling_rotational_chop', + 13: 'kneeling_rotational_reverse_chop', + 14: 'kneeling_stability_chop', + 15: 'kneeling_woodchopper', + 16: 'medicine_ball_wood_chops', + 17: 'power_squat_chops', + 18: 'weighted_power_squat_chops', + 19: 'standing_rotational_chop', + 20: 'standing_split_rotational_chop', + 21: 'standing_split_rotational_reverse_chop', + 22: 'standing_stability_reverse_chop', + }, + ), 'comm_timeout_type': FieldType( name='comm_timeout_type', base_type=BASE_TYPES[0x84], # uint16 @@ -396,6 +535,58 @@ 0x80000000: 'instant_input', # Device supports instant input feature }, ), + 'core_exercise_name': FieldType( + name='core_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'abs_jabs', + 1: 'weighted_abs_jabs', + 2: 'alternating_plate_reach', + 3: 'barbell_rollout', + 4: 'weighted_barbell_rollout', + 5: 'body_bar_oblique_twist', + 6: 'cable_core_press', + 7: 'cable_side_bend', + 8: 'side_bend', + 9: 'weighted_side_bend', + 10: 'crescent_circle', + 11: 'weighted_crescent_circle', + 12: 'cycling_russian_twist', + 13: 'weighted_cycling_russian_twist', + 14: 'elevated_feet_russian_twist', + 15: 'weighted_elevated_feet_russian_twist', + 16: 'half_turkish_get_up', + 17: 'kettlebell_windmill', + 18: 'kneeling_ab_wheel', + 19: 'weighted_kneeling_ab_wheel', + 20: 'modified_front_lever', + 21: 'open_knee_tucks', + 22: 'weighted_open_knee_tucks', + 23: 'side_abs_leg_lift', + 24: 'weighted_side_abs_leg_lift', + 25: 'swiss_ball_jackknife', + 26: 'weighted_swiss_ball_jackknife', + 27: 'swiss_ball_pike', + 28: 'weighted_swiss_ball_pike', + 29: 'swiss_ball_rollout', + 30: 'weighted_swiss_ball_rollout', + 31: 'triangle_hip_press', + 32: 'weighted_triangle_hip_press', + 33: 'trx_suspended_jackknife', + 34: 'weighted_trx_suspended_jackknife', + 35: 'u_boat', + 36: 'weighted_u_boat', + 37: 'windmill_switches', + 38: 'weighted_windmill_switches', + 39: 'alternating_slide_out', + 40: 'weighted_alternating_slide_out', + 41: 'ghd_back_extensions', + 42: 'weighted_ghd_back_extensions', + 43: 'overhead_walk', + 44: 'inchworm', + 45: 'weighted_modified_front_lever', + }, + ), 'course_capabilities': FieldType( name='course_capabilities', base_type=BASE_TYPES[0x8C], # uint32z @@ -445,6 +636,146 @@ 25: 'segment_end', }, ), + 'crunch_exercise_name': FieldType( + name='crunch_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'bicycle_crunch', + 1: 'cable_crunch', + 2: 'circular_arm_crunch', + 3: 'crossed_arms_crunch', + 4: 'weighted_crossed_arms_crunch', + 5: 'cross_leg_reverse_crunch', + 6: 'weighted_cross_leg_reverse_crunch', + 7: 'crunch_chop', + 8: 'weighted_crunch_chop', + 9: 'double_crunch', + 10: 'weighted_double_crunch', + 11: 'elbow_to_knee_crunch', + 12: 'weighted_elbow_to_knee_crunch', + 13: 'flutter_kicks', + 14: 'weighted_flutter_kicks', + 15: 'foam_roller_reverse_crunch_on_bench', + 16: 'weighted_foam_roller_reverse_crunch_on_bench', + 17: 'foam_roller_reverse_crunch_with_dumbbell', + 18: 'foam_roller_reverse_crunch_with_medicine_ball', + 19: 'frog_press', + 20: 'hanging_knee_raise_oblique_crunch', + 21: 'weighted_hanging_knee_raise_oblique_crunch', + 22: 'hip_crossover', + 23: 'weighted_hip_crossover', + 24: 'hollow_rock', + 25: 'weighted_hollow_rock', + 26: 'incline_reverse_crunch', + 27: 'weighted_incline_reverse_crunch', + 28: 'kneeling_cable_crunch', + 29: 'kneeling_cross_crunch', + 30: 'weighted_kneeling_cross_crunch', + 31: 'kneeling_oblique_cable_crunch', + 32: 'knees_to_elbow', + 33: 'leg_extensions', + 34: 'weighted_leg_extensions', + 35: 'leg_levers', + 36: 'mcgill_curl_up', + 37: 'weighted_mcgill_curl_up', + 38: 'modified_pilates_roll_up_with_ball', + 39: 'weighted_modified_pilates_roll_up_with_ball', + 40: 'pilates_crunch', + 41: 'weighted_pilates_crunch', + 42: 'pilates_roll_up_with_ball', + 43: 'weighted_pilates_roll_up_with_ball', + 44: 'raised_legs_crunch', + 45: 'weighted_raised_legs_crunch', + 46: 'reverse_crunch', + 47: 'weighted_reverse_crunch', + 48: 'reverse_crunch_on_a_bench', + 49: 'weighted_reverse_crunch_on_a_bench', + 50: 'reverse_curl_and_lift', + 51: 'weighted_reverse_curl_and_lift', + 52: 'rotational_lift', + 53: 'weighted_rotational_lift', + 54: 'seated_alternating_reverse_crunch', + 55: 'weighted_seated_alternating_reverse_crunch', + 56: 'seated_leg_u', + 57: 'weighted_seated_leg_u', + 58: 'side_to_side_crunch_and_weave', + 59: 'weighted_side_to_side_crunch_and_weave', + 60: 'single_leg_reverse_crunch', + 61: 'weighted_single_leg_reverse_crunch', + 62: 'skater_crunch_cross', + 63: 'weighted_skater_crunch_cross', + 64: 'standing_cable_crunch', + 65: 'standing_side_crunch', + 66: 'step_climb', + 67: 'weighted_step_climb', + 68: 'swiss_ball_crunch', + 69: 'swiss_ball_reverse_crunch', + 70: 'weighted_swiss_ball_reverse_crunch', + 71: 'swiss_ball_russian_twist', + 72: 'weighted_swiss_ball_russian_twist', + 73: 'swiss_ball_side_crunch', + 74: 'weighted_swiss_ball_side_crunch', + 75: 'thoracic_crunches_on_foam_roller', + 76: 'weighted_thoracic_crunches_on_foam_roller', + 77: 'triceps_crunch', + 78: 'weighted_bicycle_crunch', + 79: 'weighted_crunch', + 80: 'weighted_swiss_ball_crunch', + 81: 'toes_to_bar', + 82: 'weighted_toes_to_bar', + 83: 'crunch', + }, + ), + 'curl_exercise_name': FieldType( + name='curl_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'alternating_dumbbell_biceps_curl', + 1: 'alternating_dumbbell_biceps_curl_on_swiss_ball', + 2: 'alternating_incline_dumbbell_biceps_curl', + 3: 'barbell_biceps_curl', + 4: 'barbell_reverse_wrist_curl', + 5: 'barbell_wrist_curl', + 6: 'behind_the_back_barbell_reverse_wrist_curl', + 7: 'behind_the_back_one_arm_cable_curl', + 8: 'cable_biceps_curl', + 9: 'cable_hammer_curl', + 10: 'cheating_barbell_biceps_curl', + 11: 'close_grip_ez_bar_biceps_curl', + 12: 'cross_body_dumbbell_hammer_curl', + 13: 'dead_hang_biceps_curl', + 14: 'decline_hammer_curl', + 15: 'dumbbell_biceps_curl_with_static_hold', + 16: 'dumbbell_hammer_curl', + 17: 'dumbbell_reverse_wrist_curl', + 18: 'dumbbell_wrist_curl', + 19: 'ez_bar_preacher_curl', + 20: 'forward_bend_biceps_curl', + 21: 'hammer_curl_to_press', + 22: 'incline_dumbbell_biceps_curl', + 23: 'incline_offset_thumb_dumbbell_curl', + 24: 'kettlebell_biceps_curl', + 25: 'lying_concentration_cable_curl', + 26: 'one_arm_preacher_curl', + 27: 'plate_pinch_curl', + 28: 'preacher_curl_with_cable', + 29: 'reverse_ez_bar_curl', + 30: 'reverse_grip_wrist_curl', + 31: 'reverse_grip_barbell_biceps_curl', + 32: 'seated_alternating_dumbbell_biceps_curl', + 33: 'seated_dumbbell_biceps_curl', + 34: 'seated_reverse_dumbbell_curl', + 35: 'split_stance_offset_pinky_dumbbell_curl', + 36: 'standing_alternating_dumbbell_curls', + 37: 'standing_dumbbell_biceps_curl', + 38: 'standing_ez_bar_biceps_curl', + 39: 'static_curl', + 40: 'swiss_ball_dumbbell_overhead_triceps_extension', + 41: 'swiss_ball_ez_bar_preacher_curl', + 42: 'twisting_standing_dumbbell_biceps_curl', + 43: 'wide_grip_ez_bar_biceps_curl', + }, + ), 'date_mode': FieldType( name='date_mode', base_type=BASE_TYPES[0x00], # enum @@ -470,6 +801,31 @@ 6: 'saturday', }, ), + 'deadlift_exercise_name': FieldType( + name='deadlift_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'barbell_deadlift', + 1: 'barbell_straight_leg_deadlift', + 2: 'dumbbell_deadlift', + 3: 'dumbbell_single_leg_deadlift_to_row', + 4: 'dumbbell_straight_leg_deadlift', + 5: 'kettlebell_floor_to_shelf', + 6: 'one_arm_one_leg_deadlift', + 7: 'rack_pull', + 8: 'rotational_dumbbell_straight_leg_deadlift', + 9: 'single_arm_deadlift', + 10: 'single_leg_barbell_deadlift', + 11: 'single_leg_barbell_straight_leg_deadlift', + 12: 'single_leg_deadlift_with_barbell', + 13: 'single_leg_rdl_circuit', + 14: 'single_leg_romanian_deadlift_with_dumbbell', + 15: 'sumo_deadlift', + 16: 'sumo_deadlift_high_pull', + 17: 'trap_bar_deadlift', + 18: 'wide_grip_barbell_deadlift', + }, + ), 'device_index': FieldType( name='device_index', base_type=BASE_TYPES[0x02], # uint8 @@ -571,6 +927,31 @@ 1: 'percent_ftp', }, ), + 'dive_alarm_type': FieldType( + name='dive_alarm_type', + base_type=BASE_TYPES[0x00], # enum + values={ + 0: 'depth', + 1: 'time', + }, + ), + 'dive_backlight_mode': FieldType( + name='dive_backlight_mode', + base_type=BASE_TYPES[0x00], # enum + values={ + 0: 'at_depth', + 1: 'always_on', + }, + ), + 'dive_gas_status': FieldType( + name='dive_gas_status', + base_type=BASE_TYPES[0x00], # enum + values={ + 0: 'disabled', + 1: 'enabled', + 2: 'backup_only', + }, + ), 'event': FieldType( name='event', base_type=BASE_TYPES[0x00], # enum @@ -785,6 +1166,7 @@ 93: 'vmg', 94: 'ambient_pressure', 95: 'pressure', + 96: 'vam', }, ), 'exd_display_type': FieldType( @@ -856,6 +1238,7 @@ 31: 'last_sport', 32: 'moving', 33: 'stopped', + 34: 'estimated_total', 242: 'zone_9', 243: 'zone_8', 244: 'zone_7', @@ -867,6 +1250,46 @@ 250: 'zone_1', }, ), + 'exercise_category': FieldType( + name='exercise_category', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'bench_press', + 1: 'calf_raise', + 2: 'cardio', + 3: 'carry', + 4: 'chop', + 5: 'core', + 6: 'crunch', + 7: 'curl', + 8: 'deadlift', + 9: 'flye', + 10: 'hip_raise', + 11: 'hip_stability', + 12: 'hip_swing', + 13: 'hyperextension', + 14: 'lateral_raise', + 15: 'leg_curl', + 16: 'leg_raise', + 17: 'lunge', + 18: 'olympic_lift', + 19: 'plank', + 20: 'plyo', + 21: 'pull_up', + 22: 'push_up', + 23: 'row', + 24: 'shoulder_press', + 25: 'shoulder_stability', + 26: 'shrug', + 27: 'sit_up', + 28: 'squat', + 29: 'total_body', + 30: 'triceps_extension', + 31: 'warm_up', + 32: 'run', + 65534: 'unknown', + }, + ), 'file': FieldType( name='file', base_type=BASE_TYPES[0x00], # enum @@ -944,6 +1367,20 @@ 3: 'unknown', # lost connection to fitness equipment }, ), + 'flye_exercise_name': FieldType( + name='flye_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'cable_crossover', + 1: 'decline_dumbbell_flye', + 2: 'dumbbell_flye', + 3: 'incline_dumbbell_flye', + 4: 'kettlebell_flye', + 5: 'kneeling_rear_flye', + 6: 'single_arm_standing_cable_reverse_flye', + 7: 'swiss_ball_dumbbell_flye', + }, + ), 'garmin_product': FieldType( name='garmin_product', base_type=BASE_TYPES[0x84], # uint16 @@ -1093,6 +1530,7 @@ 2531: 'edge_explore_820', 2544: 'fenix5s', 2547: 'd2_bravo_titanium', + 2567: 'varia_ut800', # Varia UT 800 SW 2593: 'running_dynamics_pod', 2604: 'fenix5x', 2606: 'vivo_fit_jr', @@ -1148,6 +1586,104 @@ 2: 'user', # Manually generated }, ), + 'hip_raise_exercise_name': FieldType( + name='hip_raise_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'barbell_hip_thrust_on_floor', + 1: 'barbell_hip_thrust_with_bench', + 2: 'bent_knee_swiss_ball_reverse_hip_raise', + 3: 'weighted_bent_knee_swiss_ball_reverse_hip_raise', + 4: 'bridge_with_leg_extension', + 5: 'weighted_bridge_with_leg_extension', + 6: 'clam_bridge', + 7: 'front_kick_tabletop', + 8: 'weighted_front_kick_tabletop', + 9: 'hip_extension_and_cross', + 10: 'weighted_hip_extension_and_cross', + 11: 'hip_raise', + 12: 'weighted_hip_raise', + 13: 'hip_raise_with_feet_on_swiss_ball', + 14: 'weighted_hip_raise_with_feet_on_swiss_ball', + 15: 'hip_raise_with_head_on_bosu_ball', + 16: 'weighted_hip_raise_with_head_on_bosu_ball', + 17: 'hip_raise_with_head_on_swiss_ball', + 18: 'weighted_hip_raise_with_head_on_swiss_ball', + 19: 'hip_raise_with_knee_squeeze', + 20: 'weighted_hip_raise_with_knee_squeeze', + 21: 'incline_rear_leg_extension', + 22: 'weighted_incline_rear_leg_extension', + 23: 'kettlebell_swing', + 24: 'marching_hip_raise', + 25: 'weighted_marching_hip_raise', + 26: 'marching_hip_raise_with_feet_on_a_swiss_ball', + 27: 'weighted_marching_hip_raise_with_feet_on_a_swiss_ball', + 28: 'reverse_hip_raise', + 29: 'weighted_reverse_hip_raise', + 30: 'single_leg_hip_raise', + 31: 'weighted_single_leg_hip_raise', + 32: 'single_leg_hip_raise_with_foot_on_bench', + 33: 'weighted_single_leg_hip_raise_with_foot_on_bench', + 34: 'single_leg_hip_raise_with_foot_on_bosu_ball', + 35: 'weighted_single_leg_hip_raise_with_foot_on_bosu_ball', + 36: 'single_leg_hip_raise_with_foot_on_foam_roller', + 37: 'weighted_single_leg_hip_raise_with_foot_on_foam_roller', + 38: 'single_leg_hip_raise_with_foot_on_medicine_ball', + 39: 'weighted_single_leg_hip_raise_with_foot_on_medicine_ball', + 40: 'single_leg_hip_raise_with_head_on_bosu_ball', + 41: 'weighted_single_leg_hip_raise_with_head_on_bosu_ball', + 42: 'weighted_clam_bridge', + }, + ), + 'hip_stability_exercise_name': FieldType( + name='hip_stability_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'band_side_lying_leg_raise', + 1: 'dead_bug', + 2: 'weighted_dead_bug', + 3: 'external_hip_raise', + 4: 'weighted_external_hip_raise', + 5: 'fire_hydrant_kicks', + 6: 'weighted_fire_hydrant_kicks', + 7: 'hip_circles', + 8: 'weighted_hip_circles', + 9: 'inner_thigh_lift', + 10: 'weighted_inner_thigh_lift', + 11: 'lateral_walks_with_band_at_ankles', + 12: 'pretzel_side_kick', + 13: 'weighted_pretzel_side_kick', + 14: 'prone_hip_internal_rotation', + 15: 'weighted_prone_hip_internal_rotation', + 16: 'quadruped', + 17: 'quadruped_hip_extension', + 18: 'weighted_quadruped_hip_extension', + 19: 'quadruped_with_leg_lift', + 20: 'weighted_quadruped_with_leg_lift', + 21: 'side_lying_leg_raise', + 22: 'weighted_side_lying_leg_raise', + 23: 'sliding_hip_adduction', + 24: 'weighted_sliding_hip_adduction', + 25: 'standing_adduction', + 26: 'weighted_standing_adduction', + 27: 'standing_cable_hip_abduction', + 28: 'standing_hip_abduction', + 29: 'weighted_standing_hip_abduction', + 30: 'standing_rear_leg_raise', + 31: 'weighted_standing_rear_leg_raise', + 32: 'supine_hip_internal_rotation', + 33: 'weighted_supine_hip_internal_rotation', + }, + ), + 'hip_swing_exercise_name': FieldType( + name='hip_swing_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'single_arm_kettlebell_swing', + 1: 'single_arm_dumbbell_swing', + 2: 'step_out_swing', + }, + ), 'hr_type': FieldType( name='hr_type', base_type=BASE_TYPES[0x00], # enum @@ -1165,6 +1701,49 @@ 2: 'percent_hrr', }, ), + 'hyperextension_exercise_name': FieldType( + name='hyperextension_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'back_extension_with_opposite_arm_and_leg_reach', + 1: 'weighted_back_extension_with_opposite_arm_and_leg_reach', + 2: 'base_rotations', + 3: 'weighted_base_rotations', + 4: 'bent_knee_reverse_hyperextension', + 5: 'weighted_bent_knee_reverse_hyperextension', + 6: 'hollow_hold_and_roll', + 7: 'weighted_hollow_hold_and_roll', + 8: 'kicks', + 9: 'weighted_kicks', + 10: 'knee_raises', + 11: 'weighted_knee_raises', + 12: 'kneeling_superman', + 13: 'weighted_kneeling_superman', + 14: 'lat_pull_down_with_row', + 15: 'medicine_ball_deadlift_to_reach', + 16: 'one_arm_one_leg_row', + 17: 'one_arm_row_with_band', + 18: 'overhead_lunge_with_medicine_ball', + 19: 'plank_knee_tucks', + 20: 'weighted_plank_knee_tucks', + 21: 'side_step', + 22: 'weighted_side_step', + 23: 'single_leg_back_extension', + 24: 'weighted_single_leg_back_extension', + 25: 'spine_extension', + 26: 'weighted_spine_extension', + 27: 'static_back_extension', + 28: 'weighted_static_back_extension', + 29: 'superman_from_floor', + 30: 'weighted_superman_from_floor', + 31: 'swiss_ball_back_extension', + 32: 'weighted_swiss_ball_back_extension', + 33: 'swiss_ball_hyperextension', + 34: 'weighted_swiss_ball_hyperextension', + 35: 'swiss_ball_opposite_arm_and_leg_lift', + 36: 'weighted_swiss_ball_opposite_arm_and_leg_lift', + }, + ), 'intensity': FieldType( name='intensity', base_type=BASE_TYPES[0x00], # enum @@ -1303,6 +1882,44 @@ 8: 'fitness_equipment', }, ), + 'lateral_raise_exercise_name': FieldType( + name='lateral_raise_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: '45_degree_cable_external_rotation', + 1: 'alternating_lateral_raise_with_static_hold', + 2: 'bar_muscle_up', + 3: 'bent_over_lateral_raise', + 4: 'cable_diagonal_raise', + 5: 'cable_front_raise', + 6: 'calorie_row', + 7: 'combo_shoulder_raise', + 8: 'dumbbell_diagonal_raise', + 9: 'dumbbell_v_raise', + 10: 'front_raise', + 11: 'leaning_dumbbell_lateral_raise', + 12: 'lying_dumbbell_raise', + 13: 'muscle_up', + 14: 'one_arm_cable_lateral_raise', + 15: 'overhand_grip_rear_lateral_raise', + 16: 'plate_raises', + 17: 'ring_dip', + 18: 'weighted_ring_dip', + 19: 'ring_muscle_up', + 20: 'weighted_ring_muscle_up', + 21: 'rope_climb', + 22: 'weighted_rope_climb', + 23: 'scaption', + 24: 'seated_lateral_raise', + 25: 'seated_rear_lateral_raise', + 26: 'side_lying_lateral_raise', + 27: 'standing_lift', + 28: 'suspended_row', + 29: 'underhand_grip_rear_lateral_raise', + 30: 'wall_slide', + 31: 'weighted_wall_slide', + }, + ), 'left_right_balance': FieldType( name='left_right_balance', base_type=BASE_TYPES[0x02], # uint8 @@ -1319,6 +1936,52 @@ 0x8000: 'right', # data corresponds to right if set, otherwise unknown }, ), + 'leg_curl_exercise_name': FieldType( + name='leg_curl_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'leg_curl', + 1: 'weighted_leg_curl', + 2: 'good_morning', + 3: 'seated_barbell_good_morning', + 4: 'single_leg_barbell_good_morning', + 5: 'single_leg_sliding_leg_curl', + 6: 'sliding_leg_curl', + 7: 'split_barbell_good_morning', + 8: 'split_stance_extension', + 9: 'staggered_stance_good_morning', + 10: 'swiss_ball_hip_raise_and_leg_curl', + 11: 'zercher_good_morning', + }, + ), + 'leg_raise_exercise_name': FieldType( + name='leg_raise_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'hanging_knee_raise', + 1: 'hanging_leg_raise', + 2: 'weighted_hanging_leg_raise', + 3: 'hanging_single_leg_raise', + 4: 'weighted_hanging_single_leg_raise', + 5: 'kettlebell_leg_raises', + 6: 'leg_lowering_drill', + 7: 'weighted_leg_lowering_drill', + 8: 'lying_straight_leg_raise', + 9: 'weighted_lying_straight_leg_raise', + 10: 'medicine_ball_leg_drops', + 11: 'quadruped_leg_raise', + 12: 'weighted_quadruped_leg_raise', + 13: 'reverse_leg_raise', + 14: 'weighted_reverse_leg_raise', + 15: 'reverse_leg_raise_on_swiss_ball', + 16: 'weighted_reverse_leg_raise_on_swiss_ball', + 17: 'single_leg_lowering_drill', + 18: 'weighted_single_leg_lowering_drill', + 19: 'weighted_hanging_knee_raise', + 20: 'lateral_stepover', + 21: 'weighted_lateral_stepover', + }, + ), 'length_type': FieldType( name='length_type', base_type=BASE_TYPES[0x00], # enum @@ -1334,10 +1997,101 @@ 0x10000000: 'min', # if date_time is < 0x10000000 then it is system time (seconds from device power on) }, ), + 'local_device_type': FieldType( + name='local_device_type', + base_type=BASE_TYPES[0x02], # uint8 + ), 'localtime_into_day': FieldType( # number of seconds into the day since local 00:00:00 name='localtime_into_day', base_type=BASE_TYPES[0x86], # uint32 ), + 'lunge_exercise_name': FieldType( + name='lunge_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'overhead_lunge', + 1: 'lunge_matrix', + 2: 'weighted_lunge_matrix', + 3: 'alternating_barbell_forward_lunge', + 4: 'alternating_dumbbell_lunge_with_reach', + 5: 'back_foot_elevated_dumbbell_split_squat', + 6: 'barbell_box_lunge', + 7: 'barbell_bulgarian_split_squat', + 8: 'barbell_crossover_lunge', + 9: 'barbell_front_split_squat', + 10: 'barbell_lunge', + 11: 'barbell_reverse_lunge', + 12: 'barbell_side_lunge', + 13: 'barbell_split_squat', + 14: 'core_control_rear_lunge', + 15: 'diagonal_lunge', + 16: 'drop_lunge', + 17: 'dumbbell_box_lunge', + 18: 'dumbbell_bulgarian_split_squat', + 19: 'dumbbell_crossover_lunge', + 20: 'dumbbell_diagonal_lunge', + 21: 'dumbbell_lunge', + 22: 'dumbbell_lunge_and_rotation', + 23: 'dumbbell_overhead_bulgarian_split_squat', + 24: 'dumbbell_reverse_lunge_to_high_knee_and_press', + 25: 'dumbbell_side_lunge', + 26: 'elevated_front_foot_barbell_split_squat', + 27: 'front_foot_elevated_dumbbell_split_squat', + 28: 'gunslinger_lunge', + 29: 'lawnmower_lunge', + 30: 'low_lunge_with_isometric_adduction', + 31: 'low_side_to_side_lunge', + 32: 'lunge', + 33: 'weighted_lunge', + 34: 'lunge_with_arm_reach', + 35: 'lunge_with_diagonal_reach', + 36: 'lunge_with_side_bend', + 37: 'offset_dumbbell_lunge', + 38: 'offset_dumbbell_reverse_lunge', + 39: 'overhead_bulgarian_split_squat', + 40: 'overhead_dumbbell_reverse_lunge', + 41: 'overhead_dumbbell_split_squat', + 42: 'overhead_lunge_with_rotation', + 43: 'reverse_barbell_box_lunge', + 44: 'reverse_box_lunge', + 45: 'reverse_dumbbell_box_lunge', + 46: 'reverse_dumbbell_crossover_lunge', + 47: 'reverse_dumbbell_diagonal_lunge', + 48: 'reverse_lunge_with_reach_back', + 49: 'weighted_reverse_lunge_with_reach_back', + 50: 'reverse_lunge_with_twist_and_overhead_reach', + 51: 'weighted_reverse_lunge_with_twist_and_overhead_reach', + 52: 'reverse_sliding_box_lunge', + 53: 'weighted_reverse_sliding_box_lunge', + 54: 'reverse_sliding_lunge', + 55: 'weighted_reverse_sliding_lunge', + 56: 'runners_lunge_to_balance', + 57: 'weighted_runners_lunge_to_balance', + 58: 'shifting_side_lunge', + 59: 'side_and_crossover_lunge', + 60: 'weighted_side_and_crossover_lunge', + 61: 'side_lunge', + 62: 'weighted_side_lunge', + 63: 'side_lunge_and_press', + 64: 'side_lunge_jump_off', + 65: 'side_lunge_sweep', + 66: 'weighted_side_lunge_sweep', + 67: 'side_lunge_to_crossover_tap', + 68: 'weighted_side_lunge_to_crossover_tap', + 69: 'side_to_side_lunge_chops', + 70: 'weighted_side_to_side_lunge_chops', + 71: 'siff_jump_lunge', + 72: 'weighted_siff_jump_lunge', + 73: 'single_arm_reverse_lunge_and_press', + 74: 'sliding_lateral_lunge', + 75: 'weighted_sliding_lateral_lunge', + 76: 'walking_barbell_lunge', + 77: 'walking_dumbbell_lunge', + 78: 'walking_lunge', + 79: 'weighted_walking_lunge', + 80: 'wide_grip_overhead_barbell_split_squat', + }, + ), 'manufacturer': FieldType( name='manufacturer', base_type=BASE_TYPES[0x84], # uint16 @@ -1451,6 +2205,18 @@ 110: 'salutron', 111: 'technogym', 112: 'bryton_sensors', + 113: 'latitude_limited', + 114: 'soaring_technology', + 115: 'igpsport', + 116: 'thinkrider', + 117: 'gopher_sport', + 118: 'waterrower', + 119: 'orangetheory', + 120: 'inpeak', + 121: 'kinetic', + 122: 'johnson_health_tech', + 123: 'polar_electro', + 124: 'seesense', 255: 'development', 257: 'healthandlife', 258: 'lezyne', @@ -1473,6 +2239,22 @@ 275: 'podoon', 276: 'life_time_fitness', 277: 'falco_e_motors', # Falco eMotors Inc. + 278: 'minoura', + 279: 'cycliq', + 280: 'luxottica', + 281: 'trainer_road', + 282: 'the_sufferfest', + 283: 'fullspeedahead', + 284: 'virtualtraining', + 285: 'feedbacksports', + 286: 'omata', + 287: 'vdo', + 288: 'magneticdays', + 289: 'hammerhead', + 290: 'kinetic_by_kurt', + 291: 'shapelog', + 292: 'dabuziduo', + 293: 'jetblack', 5759: 'actigraphcorp', }, ), @@ -1566,6 +2348,15 @@ 206: 'field_description', 207: 'developer_data_id', 208: 'magnetometer_data', + 209: 'barometer_data', + 210: 'one_d_sensor_calibration', + 225: 'set', + 227: 'stress_level', + 258: 'dive_settings', + 259: 'dive_gas', + 262: 'dive_alarm', + 264: 'exercise_title', + 268: 'dive_summary', }, ), 'message_index': FieldType( @@ -1577,6 +2368,206 @@ 0x8000: 'selected', # message is selected if set }, ), + 'olympic_lift_exercise_name': FieldType( + name='olympic_lift_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'barbell_hang_power_clean', + 1: 'barbell_hang_squat_clean', + 2: 'barbell_power_clean', + 3: 'barbell_power_snatch', + 4: 'barbell_squat_clean', + 5: 'clean_and_jerk', + 6: 'barbell_hang_power_snatch', + 7: 'barbell_hang_pull', + 8: 'barbell_high_pull', + 9: 'barbell_snatch', + 10: 'barbell_split_jerk', + 11: 'clean', + 12: 'dumbbell_clean', + 13: 'dumbbell_hang_pull', + 14: 'one_hand_dumbbell_split_snatch', + 15: 'push_jerk', + 16: 'single_arm_dumbbell_snatch', + 17: 'single_arm_hang_snatch', + 18: 'single_arm_kettlebell_snatch', + 19: 'split_jerk', + 20: 'squat_clean_and_jerk', + }, + ), + 'plank_exercise_name': FieldType( + name='plank_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: '45_degree_plank', + 1: 'weighted_45_degree_plank', + 2: '90_degree_static_hold', + 3: 'weighted_90_degree_static_hold', + 4: 'bear_crawl', + 5: 'weighted_bear_crawl', + 6: 'cross_body_mountain_climber', + 7: 'weighted_cross_body_mountain_climber', + 8: 'elbow_plank_pike_jacks', + 9: 'weighted_elbow_plank_pike_jacks', + 10: 'elevated_feet_plank', + 11: 'weighted_elevated_feet_plank', + 12: 'elevator_abs', + 13: 'weighted_elevator_abs', + 14: 'extended_plank', + 15: 'weighted_extended_plank', + 16: 'full_plank_passe_twist', + 17: 'weighted_full_plank_passe_twist', + 18: 'inching_elbow_plank', + 19: 'weighted_inching_elbow_plank', + 20: 'inchworm_to_side_plank', + 21: 'weighted_inchworm_to_side_plank', + 22: 'kneeling_plank', + 23: 'weighted_kneeling_plank', + 24: 'kneeling_side_plank_with_leg_lift', + 25: 'weighted_kneeling_side_plank_with_leg_lift', + 26: 'lateral_roll', + 27: 'weighted_lateral_roll', + 28: 'lying_reverse_plank', + 29: 'weighted_lying_reverse_plank', + 30: 'medicine_ball_mountain_climber', + 31: 'weighted_medicine_ball_mountain_climber', + 32: 'modified_mountain_climber_and_extension', + 33: 'weighted_modified_mountain_climber_and_extension', + 34: 'mountain_climber', + 35: 'weighted_mountain_climber', + 36: 'mountain_climber_on_sliding_discs', + 37: 'weighted_mountain_climber_on_sliding_discs', + 38: 'mountain_climber_with_feet_on_bosu_ball', + 39: 'weighted_mountain_climber_with_feet_on_bosu_ball', + 40: 'mountain_climber_with_hands_on_bench', + 41: 'mountain_climber_with_hands_on_swiss_ball', + 42: 'weighted_mountain_climber_with_hands_on_swiss_ball', + 43: 'plank', + 44: 'plank_jacks_with_feet_on_sliding_discs', + 45: 'weighted_plank_jacks_with_feet_on_sliding_discs', + 46: 'plank_knee_twist', + 47: 'weighted_plank_knee_twist', + 48: 'plank_pike_jumps', + 49: 'weighted_plank_pike_jumps', + 50: 'plank_pikes', + 51: 'weighted_plank_pikes', + 52: 'plank_to_stand_up', + 53: 'weighted_plank_to_stand_up', + 54: 'plank_with_arm_raise', + 55: 'weighted_plank_with_arm_raise', + 56: 'plank_with_knee_to_elbow', + 57: 'weighted_plank_with_knee_to_elbow', + 58: 'plank_with_oblique_crunch', + 59: 'weighted_plank_with_oblique_crunch', + 60: 'plyometric_side_plank', + 61: 'weighted_plyometric_side_plank', + 62: 'rolling_side_plank', + 63: 'weighted_rolling_side_plank', + 64: 'side_kick_plank', + 65: 'weighted_side_kick_plank', + 66: 'side_plank', + 67: 'weighted_side_plank', + 68: 'side_plank_and_row', + 69: 'weighted_side_plank_and_row', + 70: 'side_plank_lift', + 71: 'weighted_side_plank_lift', + 72: 'side_plank_with_elbow_on_bosu_ball', + 73: 'weighted_side_plank_with_elbow_on_bosu_ball', + 74: 'side_plank_with_feet_on_bench', + 75: 'weighted_side_plank_with_feet_on_bench', + 76: 'side_plank_with_knee_circle', + 77: 'weighted_side_plank_with_knee_circle', + 78: 'side_plank_with_knee_tuck', + 79: 'weighted_side_plank_with_knee_tuck', + 80: 'side_plank_with_leg_lift', + 81: 'weighted_side_plank_with_leg_lift', + 82: 'side_plank_with_reach_under', + 83: 'weighted_side_plank_with_reach_under', + 84: 'single_leg_elevated_feet_plank', + 85: 'weighted_single_leg_elevated_feet_plank', + 86: 'single_leg_flex_and_extend', + 87: 'weighted_single_leg_flex_and_extend', + 88: 'single_leg_side_plank', + 89: 'weighted_single_leg_side_plank', + 90: 'spiderman_plank', + 91: 'weighted_spiderman_plank', + 92: 'straight_arm_plank', + 93: 'weighted_straight_arm_plank', + 94: 'straight_arm_plank_with_shoulder_touch', + 95: 'weighted_straight_arm_plank_with_shoulder_touch', + 96: 'swiss_ball_plank', + 97: 'weighted_swiss_ball_plank', + 98: 'swiss_ball_plank_leg_lift', + 99: 'weighted_swiss_ball_plank_leg_lift', + 100: 'swiss_ball_plank_leg_lift_and_hold', + 101: 'swiss_ball_plank_with_feet_on_bench', + 102: 'weighted_swiss_ball_plank_with_feet_on_bench', + 103: 'swiss_ball_prone_jackknife', + 104: 'weighted_swiss_ball_prone_jackknife', + 105: 'swiss_ball_side_plank', + 106: 'weighted_swiss_ball_side_plank', + 107: 'three_way_plank', + 108: 'weighted_three_way_plank', + 109: 'towel_plank_and_knee_in', + 110: 'weighted_towel_plank_and_knee_in', + 111: 't_stabilization', + 112: 'weighted_t_stabilization', + 113: 'turkish_get_up_to_side_plank', + 114: 'weighted_turkish_get_up_to_side_plank', + 115: 'two_point_plank', + 116: 'weighted_two_point_plank', + 117: 'weighted_plank', + 118: 'wide_stance_plank_with_diagonal_arm_lift', + 119: 'weighted_wide_stance_plank_with_diagonal_arm_lift', + 120: 'wide_stance_plank_with_diagonal_leg_lift', + 121: 'weighted_wide_stance_plank_with_diagonal_leg_lift', + 122: 'wide_stance_plank_with_leg_lift', + 123: 'weighted_wide_stance_plank_with_leg_lift', + 124: 'wide_stance_plank_with_opposite_arm_and_leg_lift', + 125: 'weighted_mountain_climber_with_hands_on_bench', + 126: 'weighted_swiss_ball_plank_leg_lift_and_hold', + 127: 'weighted_wide_stance_plank_with_opposite_arm_and_leg_lift', + }, + ), + 'plyo_exercise_name': FieldType( + name='plyo_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'alternating_jump_lunge', + 1: 'weighted_alternating_jump_lunge', + 2: 'barbell_jump_squat', + 3: 'body_weight_jump_squat', + 4: 'weighted_jump_squat', + 5: 'cross_knee_strike', + 6: 'weighted_cross_knee_strike', + 7: 'depth_jump', + 8: 'weighted_depth_jump', + 9: 'dumbbell_jump_squat', + 10: 'dumbbell_split_jump', + 11: 'front_knee_strike', + 12: 'weighted_front_knee_strike', + 13: 'high_box_jump', + 14: 'weighted_high_box_jump', + 15: 'isometric_explosive_body_weight_jump_squat', + 16: 'weighted_isometric_explosive_jump_squat', + 17: 'lateral_leap_and_hop', + 18: 'weighted_lateral_leap_and_hop', + 19: 'lateral_plyo_squats', + 20: 'weighted_lateral_plyo_squats', + 21: 'lateral_slide', + 22: 'weighted_lateral_slide', + 23: 'medicine_ball_overhead_throws', + 24: 'medicine_ball_side_throw', + 25: 'medicine_ball_slam', + 26: 'side_to_side_medicine_ball_throws', + 27: 'side_to_side_shuffle_jump', + 28: 'weighted_side_to_side_shuffle_jump', + 29: 'squat_jump_onto_box', + 30: 'weighted_squat_jump_onto_box', + 31: 'squat_jumps_in_and_out', + 32: 'weighted_squat_jumps_in_and_out', + }, + ), 'power_phase_type': FieldType( name='power_phase_type', base_type=BASE_TYPES[0x00], # enum @@ -1587,6 +2578,135 @@ 3: 'power_phase_center', }, ), + 'pull_up_exercise_name': FieldType( + name='pull_up_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'banded_pull_ups', + 1: '30_degree_lat_pulldown', + 2: 'band_assisted_chin_up', + 3: 'close_grip_chin_up', + 4: 'weighted_close_grip_chin_up', + 5: 'close_grip_lat_pulldown', + 6: 'crossover_chin_up', + 7: 'weighted_crossover_chin_up', + 8: 'ez_bar_pullover', + 9: 'hanging_hurdle', + 10: 'weighted_hanging_hurdle', + 11: 'kneeling_lat_pulldown', + 12: 'kneeling_underhand_grip_lat_pulldown', + 13: 'lat_pulldown', + 14: 'mixed_grip_chin_up', + 15: 'weighted_mixed_grip_chin_up', + 16: 'mixed_grip_pull_up', + 17: 'weighted_mixed_grip_pull_up', + 18: 'reverse_grip_pulldown', + 19: 'standing_cable_pullover', + 20: 'straight_arm_pulldown', + 21: 'swiss_ball_ez_bar_pullover', + 22: 'towel_pull_up', + 23: 'weighted_towel_pull_up', + 24: 'weighted_pull_up', + 25: 'wide_grip_lat_pulldown', + 26: 'wide_grip_pull_up', + 27: 'weighted_wide_grip_pull_up', + 28: 'burpee_pull_up', + 29: 'weighted_burpee_pull_up', + 30: 'jumping_pull_ups', + 31: 'weighted_jumping_pull_ups', + 32: 'kipping_pull_up', + 33: 'weighted_kipping_pull_up', + 34: 'l_pull_up', + 35: 'weighted_l_pull_up', + 36: 'suspended_chin_up', + 37: 'weighted_suspended_chin_up', + 38: 'pull_up', + }, + ), + 'push_up_exercise_name': FieldType( + name='push_up_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'chest_press_with_band', + 1: 'alternating_staggered_push_up', + 2: 'weighted_alternating_staggered_push_up', + 3: 'alternating_hands_medicine_ball_push_up', + 4: 'weighted_alternating_hands_medicine_ball_push_up', + 5: 'bosu_ball_push_up', + 6: 'weighted_bosu_ball_push_up', + 7: 'clapping_push_up', + 8: 'weighted_clapping_push_up', + 9: 'close_grip_medicine_ball_push_up', + 10: 'weighted_close_grip_medicine_ball_push_up', + 11: 'close_hands_push_up', + 12: 'weighted_close_hands_push_up', + 13: 'decline_push_up', + 14: 'weighted_decline_push_up', + 15: 'diamond_push_up', + 16: 'weighted_diamond_push_up', + 17: 'explosive_crossover_push_up', + 18: 'weighted_explosive_crossover_push_up', + 19: 'explosive_push_up', + 20: 'weighted_explosive_push_up', + 21: 'feet_elevated_side_to_side_push_up', + 22: 'weighted_feet_elevated_side_to_side_push_up', + 23: 'hand_release_push_up', + 24: 'weighted_hand_release_push_up', + 25: 'handstand_push_up', + 26: 'weighted_handstand_push_up', + 27: 'incline_push_up', + 28: 'weighted_incline_push_up', + 29: 'isometric_explosive_push_up', + 30: 'weighted_isometric_explosive_push_up', + 31: 'judo_push_up', + 32: 'weighted_judo_push_up', + 33: 'kneeling_push_up', + 34: 'weighted_kneeling_push_up', + 35: 'medicine_ball_chest_pass', + 36: 'medicine_ball_push_up', + 37: 'weighted_medicine_ball_push_up', + 38: 'one_arm_push_up', + 39: 'weighted_one_arm_push_up', + 40: 'weighted_push_up', + 41: 'push_up_and_row', + 42: 'weighted_push_up_and_row', + 43: 'push_up_plus', + 44: 'weighted_push_up_plus', + 45: 'push_up_with_feet_on_swiss_ball', + 46: 'weighted_push_up_with_feet_on_swiss_ball', + 47: 'push_up_with_one_hand_on_medicine_ball', + 48: 'weighted_push_up_with_one_hand_on_medicine_ball', + 49: 'shoulder_push_up', + 50: 'weighted_shoulder_push_up', + 51: 'single_arm_medicine_ball_push_up', + 52: 'weighted_single_arm_medicine_ball_push_up', + 53: 'spiderman_push_up', + 54: 'weighted_spiderman_push_up', + 55: 'stacked_feet_push_up', + 56: 'weighted_stacked_feet_push_up', + 57: 'staggered_hands_push_up', + 58: 'weighted_staggered_hands_push_up', + 59: 'suspended_push_up', + 60: 'weighted_suspended_push_up', + 61: 'swiss_ball_push_up', + 62: 'weighted_swiss_ball_push_up', + 63: 'swiss_ball_push_up_plus', + 64: 'weighted_swiss_ball_push_up_plus', + 65: 't_push_up', + 66: 'weighted_t_push_up', + 67: 'triple_stop_push_up', + 68: 'weighted_triple_stop_push_up', + 69: 'wide_hands_push_up', + 70: 'weighted_wide_hands_push_up', + 71: 'parallette_handstand_push_up', + 72: 'weighted_parallette_handstand_push_up', + 73: 'ring_handstand_push_up', + 74: 'weighted_ring_handstand_push_up', + 75: 'ring_push_up', + 76: 'weighted_ring_push_up', + 77: 'push_up', + }, + ), 'pwr_zone_calc': FieldType( name='pwr_zone_calc', base_type=BASE_TYPES[0x00], # enum @@ -1605,6 +2725,56 @@ 3: 'transition_to_standing', }, ), + 'row_exercise_name': FieldType( + name='row_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'barbell_straight_leg_deadlift_to_row', + 1: 'cable_row_standing', + 2: 'dumbbell_row', + 3: 'elevated_feet_inverted_row', + 4: 'weighted_elevated_feet_inverted_row', + 5: 'face_pull', + 6: 'face_pull_with_external_rotation', + 7: 'inverted_row_with_feet_on_swiss_ball', + 8: 'weighted_inverted_row_with_feet_on_swiss_ball', + 9: 'kettlebell_row', + 10: 'modified_inverted_row', + 11: 'weighted_modified_inverted_row', + 12: 'neutral_grip_alternating_dumbbell_row', + 13: 'one_arm_bent_over_row', + 14: 'one_legged_dumbbell_row', + 15: 'renegade_row', + 16: 'reverse_grip_barbell_row', + 17: 'rope_handle_cable_row', + 18: 'seated_cable_row', + 19: 'seated_dumbbell_row', + 20: 'single_arm_cable_row', + 21: 'single_arm_cable_row_and_rotation', + 22: 'single_arm_inverted_row', + 23: 'weighted_single_arm_inverted_row', + 24: 'single_arm_neutral_grip_dumbbell_row', + 25: 'single_arm_neutral_grip_dumbbell_row_and_rotation', + 26: 'suspended_inverted_row', + 27: 'weighted_suspended_inverted_row', + 28: 't_bar_row', + 29: 'towel_grip_inverted_row', + 30: 'weighted_towel_grip_inverted_row', + 31: 'underhand_grip_cable_row', + 32: 'v_grip_cable_row', + 33: 'wide_grip_seated_cable_row', + }, + ), + 'run_exercise_name': FieldType( + name='run_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'run', + 1: 'walk', + 2: 'jog', + 3: 'sprint', + }, + ), 'schedule': FieldType( name='schedule', base_type=BASE_TYPES[0x00], # enum @@ -1662,6 +2832,7 @@ 0: 'accelerometer', 1: 'gyroscope', 2: 'compass', # Magnetometer + 3: 'barometer', }, ), 'session_trigger': FieldType( @@ -1674,6 +2845,106 @@ 3: 'fitness_equipment', # Auto sport change caused by user linking to fitness equipment. }, ), + 'set_type': FieldType( + name='set_type', + base_type=BASE_TYPES[0x02], # uint8 + values={ + 0: 'rest', + 1: 'active', + }, + ), + 'shoulder_press_exercise_name': FieldType( + name='shoulder_press_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'alternating_dumbbell_shoulder_press', + 1: 'arnold_press', + 2: 'barbell_front_squat_to_push_press', + 3: 'barbell_push_press', + 4: 'barbell_shoulder_press', + 5: 'dead_curl_press', + 6: 'dumbbell_alternating_shoulder_press_and_twist', + 7: 'dumbbell_hammer_curl_to_lunge_to_press', + 8: 'dumbbell_push_press', + 9: 'floor_inverted_shoulder_press', + 10: 'weighted_floor_inverted_shoulder_press', + 11: 'inverted_shoulder_press', + 12: 'weighted_inverted_shoulder_press', + 13: 'one_arm_push_press', + 14: 'overhead_barbell_press', + 15: 'overhead_dumbbell_press', + 16: 'seated_barbell_shoulder_press', + 17: 'seated_dumbbell_shoulder_press', + 18: 'single_arm_dumbbell_shoulder_press', + 19: 'single_arm_step_up_and_press', + 20: 'smith_machine_overhead_press', + 21: 'split_stance_hammer_curl_to_press', + 22: 'swiss_ball_dumbbell_shoulder_press', + 23: 'weight_plate_front_raise', + }, + ), + 'shoulder_stability_exercise_name': FieldType( + name='shoulder_stability_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: '90_degree_cable_external_rotation', + 1: 'band_external_rotation', + 2: 'band_internal_rotation', + 3: 'bent_arm_lateral_raise_and_external_rotation', + 4: 'cable_external_rotation', + 5: 'dumbbell_face_pull_with_external_rotation', + 6: 'floor_i_raise', + 7: 'weighted_floor_i_raise', + 8: 'floor_t_raise', + 9: 'weighted_floor_t_raise', + 10: 'floor_y_raise', + 11: 'weighted_floor_y_raise', + 12: 'incline_i_raise', + 13: 'weighted_incline_i_raise', + 14: 'incline_l_raise', + 15: 'weighted_incline_l_raise', + 16: 'incline_t_raise', + 17: 'weighted_incline_t_raise', + 18: 'incline_w_raise', + 19: 'weighted_incline_w_raise', + 20: 'incline_y_raise', + 21: 'weighted_incline_y_raise', + 22: 'lying_external_rotation', + 23: 'seated_dumbbell_external_rotation', + 24: 'standing_l_raise', + 25: 'swiss_ball_i_raise', + 26: 'weighted_swiss_ball_i_raise', + 27: 'swiss_ball_t_raise', + 28: 'weighted_swiss_ball_t_raise', + 29: 'swiss_ball_w_raise', + 30: 'weighted_swiss_ball_w_raise', + 31: 'swiss_ball_y_raise', + 32: 'weighted_swiss_ball_y_raise', + }, + ), + 'shrug_exercise_name': FieldType( + name='shrug_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'barbell_jump_shrug', + 1: 'barbell_shrug', + 2: 'barbell_upright_row', + 3: 'behind_the_back_smith_machine_shrug', + 4: 'dumbbell_jump_shrug', + 5: 'dumbbell_shrug', + 6: 'dumbbell_upright_row', + 7: 'incline_dumbbell_shrug', + 8: 'overhead_barbell_shrug', + 9: 'overhead_dumbbell_shrug', + 10: 'scaption_and_shrug', + 11: 'scapular_retraction', + 12: 'serratus_chair_shrug', + 13: 'weighted_serratus_chair_shrug', + 14: 'serratus_shrug', + 15: 'weighted_serratus_shrug', + 16: 'wide_grip_jump_shrug', + }, + ), 'side': FieldType( name='side', base_type=BASE_TYPES[0x00], # enum @@ -1682,6 +2953,50 @@ 1: 'left', }, ), + 'sit_up_exercise_name': FieldType( + name='sit_up_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'alternating_sit_up', + 1: 'weighted_alternating_sit_up', + 2: 'bent_knee_v_up', + 3: 'weighted_bent_knee_v_up', + 4: 'butterfly_sit_up', + 5: 'weighted_butterfly_situp', + 6: 'cross_punch_roll_up', + 7: 'weighted_cross_punch_roll_up', + 8: 'crossed_arms_sit_up', + 9: 'weighted_crossed_arms_sit_up', + 10: 'get_up_sit_up', + 11: 'weighted_get_up_sit_up', + 12: 'hovering_sit_up', + 13: 'weighted_hovering_sit_up', + 14: 'kettlebell_sit_up', + 15: 'medicine_ball_alternating_v_up', + 16: 'medicine_ball_sit_up', + 17: 'medicine_ball_v_up', + 18: 'modified_sit_up', + 19: 'negative_sit_up', + 20: 'one_arm_full_sit_up', + 21: 'reclining_circle', + 22: 'weighted_reclining_circle', + 23: 'reverse_curl_up', + 24: 'weighted_reverse_curl_up', + 25: 'single_leg_swiss_ball_jackknife', + 26: 'weighted_single_leg_swiss_ball_jackknife', + 27: 'the_teaser', + 28: 'the_teaser_weighted', + 29: 'three_part_roll_down', + 30: 'weighted_three_part_roll_down', + 31: 'v_up', + 32: 'weighted_v_up', + 33: 'weighted_russian_twist_on_swiss_ball', + 34: 'weighted_sit_up', + 35: 'x_abs', + 36: 'weighted_x_abs', + 37: 'sit_up', + }, + ), 'source_type': FieldType( name='source_type', base_type=BASE_TYPES[0x00], # enum @@ -1856,6 +3171,99 @@ 8: 'touring', }, ), + 'squat_exercise_name': FieldType( + name='squat_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'leg_press', + 1: 'back_squat_with_body_bar', + 2: 'back_squats', + 3: 'weighted_back_squats', + 4: 'balancing_squat', + 5: 'weighted_balancing_squat', + 6: 'barbell_back_squat', + 7: 'barbell_box_squat', + 8: 'barbell_front_squat', + 9: 'barbell_hack_squat', + 10: 'barbell_hang_squat_snatch', + 11: 'barbell_lateral_step_up', + 12: 'barbell_quarter_squat', + 13: 'barbell_siff_squat', + 14: 'barbell_squat_snatch', + 15: 'barbell_squat_with_heels_raised', + 16: 'barbell_stepover', + 17: 'barbell_step_up', + 18: 'bench_squat_with_rotational_chop', + 19: 'weighted_bench_squat_with_rotational_chop', + 20: 'body_weight_wall_squat', + 21: 'weighted_wall_squat', + 22: 'box_step_squat', + 23: 'weighted_box_step_squat', + 24: 'braced_squat', + 25: 'crossed_arm_barbell_front_squat', + 26: 'crossover_dumbbell_step_up', + 27: 'dumbbell_front_squat', + 28: 'dumbbell_split_squat', + 29: 'dumbbell_squat', + 30: 'dumbbell_squat_clean', + 31: 'dumbbell_stepover', + 32: 'dumbbell_step_up', + 33: 'elevated_single_leg_squat', + 34: 'weighted_elevated_single_leg_squat', + 35: 'figure_four_squats', + 36: 'weighted_figure_four_squats', + 37: 'goblet_squat', + 38: 'kettlebell_squat', + 39: 'kettlebell_swing_overhead', + 40: 'kettlebell_swing_with_flip_to_squat', + 41: 'lateral_dumbbell_step_up', + 42: 'one_legged_squat', + 43: 'overhead_dumbbell_squat', + 44: 'overhead_squat', + 45: 'partial_single_leg_squat', + 46: 'weighted_partial_single_leg_squat', + 47: 'pistol_squat', + 48: 'weighted_pistol_squat', + 49: 'plie_slides', + 50: 'weighted_plie_slides', + 51: 'plie_squat', + 52: 'weighted_plie_squat', + 53: 'prisoner_squat', + 54: 'weighted_prisoner_squat', + 55: 'single_leg_bench_get_up', + 56: 'weighted_single_leg_bench_get_up', + 57: 'single_leg_bench_squat', + 58: 'weighted_single_leg_bench_squat', + 59: 'single_leg_squat_on_swiss_ball', + 60: 'weighted_single_leg_squat_on_swiss_ball', + 61: 'squat', + 62: 'weighted_squat', + 63: 'squats_with_band', + 64: 'staggered_squat', + 65: 'weighted_staggered_squat', + 66: 'step_up', + 67: 'weighted_step_up', + 68: 'suitcase_squats', + 69: 'sumo_squat', + 70: 'sumo_squat_slide_in', + 71: 'weighted_sumo_squat_slide_in', + 72: 'sumo_squat_to_high_pull', + 73: 'sumo_squat_to_stand', + 74: 'weighted_sumo_squat_to_stand', + 75: 'sumo_squat_with_rotation', + 76: 'weighted_sumo_squat_with_rotation', + 77: 'swiss_ball_body_weight_wall_squat', + 78: 'weighted_swiss_ball_wall_squat', + 79: 'thrusters', + 80: 'uneven_squat', + 81: 'weighted_uneven_squat', + 82: 'waist_slimming_squat', + 83: 'wall_ball', + 84: 'wide_stance_barbell_squat', + 85: 'wide_stance_goblet_squat', + 86: 'zercher_squat', + }, + ), 'stroke_type': FieldType( name='stroke_type', base_type=BASE_TYPES[0x00], # enum @@ -1896,7 +3304,7 @@ 21: 'warm_up', # Tennis 22: 'match', # Tennis 23: 'exercise', # Tennis - 24: 'challenge', # Tennis + 24: 'challenge', 25: 'indoor_skiing', # Fitness Equipment 26: 'cardio_training', # Training 27: 'indoor_walking', # Walking/Fitness Equipment @@ -1925,6 +3333,13 @@ 50: 'navigate', 51: 'track_me', 52: 'map', + 53: 'single_gas_diving', # Diving + 54: 'multi_gas_diving', # Diving + 55: 'gauge_diving', # Diving + 56: 'apnea_diving', # Diving + 57: 'apnea_hunting', # Diving + 58: 'virtual_activity', + 59: 'obstacle', # Used for events where participants run, crawl through mud, climb over walls, etc. 254: 'all', }, ), @@ -2101,6 +3516,89 @@ 2: 'fitness_equipment', }, ), + 'tissue_model_type': FieldType( + name='tissue_model_type', + base_type=BASE_TYPES[0x00], # enum + values={ + 0: 'zhl_16c', # Buhlmann's decompression algorithm, version C + }, + ), + 'tone': FieldType( + name='tone', + base_type=BASE_TYPES[0x00], # enum + values={ + 0: 'off', + 1: 'tone', + 2: 'vibrate', + 3: 'tone_and_vibrate', + }, + ), + 'total_body_exercise_name': FieldType( + name='total_body_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'burpee', + 1: 'weighted_burpee', + 2: 'burpee_box_jump', + 3: 'weighted_burpee_box_jump', + 4: 'high_pull_burpee', + 5: 'man_makers', + 6: 'one_arm_burpee', + 7: 'squat_thrusts', + 8: 'weighted_squat_thrusts', + 9: 'squat_plank_push_up', + 10: 'weighted_squat_plank_push_up', + 11: 'standing_t_rotation_balance', + 12: 'weighted_standing_t_rotation_balance', + }, + ), + 'triceps_extension_exercise_name': FieldType( + name='triceps_extension_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'bench_dip', + 1: 'weighted_bench_dip', + 2: 'body_weight_dip', + 3: 'cable_kickback', + 4: 'cable_lying_triceps_extension', + 5: 'cable_overhead_triceps_extension', + 6: 'dumbbell_kickback', + 7: 'dumbbell_lying_triceps_extension', + 8: 'ez_bar_overhead_triceps_extension', + 9: 'incline_dip', + 10: 'weighted_incline_dip', + 11: 'incline_ez_bar_lying_triceps_extension', + 12: 'lying_dumbbell_pullover_to_extension', + 13: 'lying_ez_bar_triceps_extension', + 14: 'lying_triceps_extension_to_close_grip_bench_press', + 15: 'overhead_dumbbell_triceps_extension', + 16: 'reclining_triceps_press', + 17: 'reverse_grip_pressdown', + 18: 'reverse_grip_triceps_pressdown', + 19: 'rope_pressdown', + 20: 'seated_barbell_overhead_triceps_extension', + 21: 'seated_dumbbell_overhead_triceps_extension', + 22: 'seated_ez_bar_overhead_triceps_extension', + 23: 'seated_single_arm_overhead_dumbbell_extension', + 24: 'single_arm_dumbbell_overhead_triceps_extension', + 25: 'single_dumbbell_seated_overhead_triceps_extension', + 26: 'single_leg_bench_dip_and_kick', + 27: 'weighted_single_leg_bench_dip_and_kick', + 28: 'single_leg_dip', + 29: 'weighted_single_leg_dip', + 30: 'static_lying_triceps_extension', + 31: 'suspended_dip', + 32: 'weighted_suspended_dip', + 33: 'swiss_ball_dumbbell_lying_triceps_extension', + 34: 'swiss_ball_ez_bar_lying_triceps_extension', + 35: 'swiss_ball_ez_bar_overhead_triceps_extension', + 36: 'tabletop_dip', + 37: 'weighted_tabletop_dip', + 38: 'triceps_extension_on_floor', + 39: 'triceps_pressdown', + 40: 'weighted_dip', + }, + ), 'turn_type': FieldType( name='turn_type', base_type=BASE_TYPES[0x00], # enum @@ -2157,6 +3655,43 @@ 0xFFFE: 'portable_max', }, ), + 'warm_up_exercise_name': FieldType( + name='warm_up_exercise_name', + base_type=BASE_TYPES[0x84], # uint16 + values={ + 0: 'quadruped_rocking', + 1: 'neck_tilts', + 2: 'ankle_circles', + 3: 'ankle_dorsiflexion_with_band', + 4: 'ankle_internal_rotation', + 5: 'arm_circles', + 6: 'bent_over_reach_to_sky', + 7: 'cat_camel', + 8: 'elbow_to_foot_lunge', + 9: 'forward_and_backward_leg_swings', + 10: 'groiners', + 11: 'inverted_hamstring_stretch', + 12: 'lateral_duck_under', + 13: 'neck_rotations', + 14: 'opposite_arm_and_leg_balance', + 15: 'reach_roll_and_lift', + 16: 'scorpion', + 17: 'shoulder_circles', + 18: 'side_to_side_leg_swings', + 19: 'sleeper_stretch', + 20: 'slide_out', + 21: 'swiss_ball_hip_crossover', + 22: 'swiss_ball_reach_roll_and_lift', + 23: 'swiss_ball_windshield_wipers', + 24: 'thoracic_rotation', + 25: 'walking_high_kicks', + 26: 'walking_high_knees', + 27: 'walking_knee_hugs', + 28: 'walking_leg_cradles', + 29: 'walkout', + 30: 'walkout_from_push_up_position', + }, + ), 'watchface_mode': FieldType( name='watchface_mode', base_type=BASE_TYPES[0x00], # enum @@ -2167,6 +3702,16 @@ 3: 'disabled', }, ), + 'water_type': FieldType( + name='water_type', + base_type=BASE_TYPES[0x00], # enum + values={ + 0: 'fresh', + 1: 'salt', + 2: 'en13319', + 3: 'custom', + }, + ), 'weather_report': FieldType( name='weather_report', base_type=BASE_TYPES[0x00], # enum @@ -2346,6 +3891,7 @@ 26: 'power_lap_greater_than', 27: 'repeat_until_training_peaks_tss', 28: 'repetition_time', + 29: 'reps', }, ), 'wkt_step_target': FieldType( @@ -2421,7 +3967,7 @@ MESSAGE_TYPES = { - ############################ Common Messages ############################# + # **************************** Common Messages ***************************** 0: MessageType( # Must be first message in file. name='file_id', mesg_num=0, @@ -2492,7 +4038,7 @@ ), - #################################### #################################### + # ************************************ ************************************ 1: MessageType( name='capabilities', mesg_num=1, @@ -2519,249 +4065,95 @@ ), }, ), - 3: MessageType( - name='user_profile', - mesg_num=3, + 4: MessageType( + name='hrm_profile', + mesg_num=4, fields={ 0: Field( - name='friendly_name', - type=BASE_TYPES[0x07], # string + name='enabled', + type=FIELD_TYPES['bool'], def_num=0, ), 1: Field( - name='gender', - type=FIELD_TYPES['gender'], + name='hrm_ant_id', + type=BASE_TYPES[0x8B], # uint16z def_num=1, ), 2: Field( - name='age', - type=BASE_TYPES[0x02], # uint8 + name='log_hrv', + type=FIELD_TYPES['bool'], def_num=2, - units='years', ), 3: Field( - name='height', - type=BASE_TYPES[0x02], # uint8 + name='hrm_ant_id_trans_type', + type=BASE_TYPES[0x0A], # uint8z + def_num=3, + ), + 254: Field( + name='message_index', + type=FIELD_TYPES['message_index'], + def_num=254, + ), + }, + ), + 5: MessageType( + name='sdm_profile', + mesg_num=5, + fields={ + 0: Field( + name='enabled', + type=FIELD_TYPES['bool'], + def_num=0, + ), + 1: Field( + name='sdm_ant_id', + type=BASE_TYPES[0x8B], # uint16z + def_num=1, + ), + 2: Field( + name='sdm_cal_factor', + type=BASE_TYPES[0x84], # uint16 + def_num=2, + scale=10, + units='%', + ), + 3: Field( + name='odometer', + type=BASE_TYPES[0x86], # uint32 def_num=3, scale=100, units='m', ), - 4: Field( - name='weight', - type=BASE_TYPES[0x84], # uint16 + 4: Field( # Use footpod for speed source instead of GPS + name='speed_source', + type=FIELD_TYPES['bool'], def_num=4, - scale=10, - units='kg', ), 5: Field( - name='language', - type=FIELD_TYPES['language'], + name='sdm_ant_id_trans_type', + type=BASE_TYPES[0x0A], # uint8z def_num=5, ), - 6: Field( - name='elev_setting', - type=FIELD_TYPES['display_measure'], - def_num=6, - ), - 7: Field( - name='weight_setting', - type=FIELD_TYPES['display_measure'], + 7: Field( # Rollover counter that can be used to extend the odometer + name='odometer_rollover', + type=BASE_TYPES[0x02], # uint8 def_num=7, ), - 8: Field( - name='resting_heart_rate', - type=BASE_TYPES[0x02], # uint8 - def_num=8, - units='bpm', + 254: Field( + name='message_index', + type=FIELD_TYPES['message_index'], + def_num=254, ), - 9: Field( - name='default_max_running_heart_rate', - type=BASE_TYPES[0x02], # uint8 - def_num=9, - units='bpm', - ), - 10: Field( - name='default_max_biking_heart_rate', - type=BASE_TYPES[0x02], # uint8 - def_num=10, - units='bpm', - ), - 11: Field( - name='default_max_heart_rate', - type=BASE_TYPES[0x02], # uint8 - def_num=11, - units='bpm', - ), - 12: Field( - name='hr_setting', - type=FIELD_TYPES['display_heart'], - def_num=12, - ), - 13: Field( - name='speed_setting', - type=FIELD_TYPES['display_measure'], - def_num=13, - ), - 14: Field( - name='dist_setting', - type=FIELD_TYPES['display_measure'], - def_num=14, - ), - 16: Field( - name='power_setting', - type=FIELD_TYPES['display_power'], - def_num=16, - ), - 17: Field( - name='activity_class', - type=FIELD_TYPES['activity_class'], - def_num=17, - ), - 18: Field( - name='position_setting', - type=FIELD_TYPES['display_position'], - def_num=18, - ), - 21: Field( - name='temperature_setting', - type=FIELD_TYPES['display_measure'], - def_num=21, - ), - 22: Field( - name='local_id', - type=FIELD_TYPES['user_local_id'], - def_num=22, - ), - 23: Field( - name='global_id', - type=BASE_TYPES[0x0D], # byte - def_num=23, - ), - 28: Field( # Typical wake time - name='wake_time', - type=FIELD_TYPES['localtime_into_day'], - def_num=28, - ), - 29: Field( # Typical bed time - name='sleep_time', - type=FIELD_TYPES['localtime_into_day'], - def_num=29, - ), - 30: Field( - name='height_setting', - type=FIELD_TYPES['display_measure'], - def_num=30, - ), - 31: Field( # User defined running step length set to 0 for auto length - name='user_running_step_length', - type=BASE_TYPES[0x84], # uint16 - def_num=31, - scale=1000, - units='m', - ), - 32: Field( # User defined walking step length set to 0 for auto length - name='user_walking_step_length', - type=BASE_TYPES[0x84], # uint16 - def_num=32, - scale=1000, - units='m', - ), - 254: Field( - name='message_index', - type=FIELD_TYPES['message_index'], - def_num=254, - ), - }, - ), - 4: MessageType( - name='hrm_profile', - mesg_num=4, - fields={ - 0: Field( - name='enabled', - type=FIELD_TYPES['bool'], - def_num=0, - ), - 1: Field( - name='hrm_ant_id', - type=BASE_TYPES[0x8B], # uint16z - def_num=1, - ), - 2: Field( - name='log_hrv', - type=FIELD_TYPES['bool'], - def_num=2, - ), - 3: Field( - name='hrm_ant_id_trans_type', - type=BASE_TYPES[0x0A], # uint8z - def_num=3, - ), - 254: Field( - name='message_index', - type=FIELD_TYPES['message_index'], - def_num=254, - ), - }, - ), - 5: MessageType( - name='sdm_profile', - mesg_num=5, - fields={ - 0: Field( - name='enabled', - type=FIELD_TYPES['bool'], - def_num=0, - ), - 1: Field( - name='sdm_ant_id', - type=BASE_TYPES[0x8B], # uint16z - def_num=1, - ), - 2: Field( - name='sdm_cal_factor', - type=BASE_TYPES[0x84], # uint16 - def_num=2, - scale=10, - units='%', - ), - 3: Field( - name='odometer', - type=BASE_TYPES[0x86], # uint32 - def_num=3, - scale=100, - units='m', - ), - 4: Field( # Use footpod for speed source instead of GPS - name='speed_source', - type=FIELD_TYPES['bool'], - def_num=4, - ), - 5: Field( - name='sdm_ant_id_trans_type', - type=BASE_TYPES[0x0A], # uint8z - def_num=5, - ), - 7: Field( # Rollover counter that can be used to extend the odometer - name='odometer_rollover', - type=BASE_TYPES[0x02], # uint8 - def_num=7, - ), - 254: Field( - name='message_index', - type=FIELD_TYPES['message_index'], - def_num=254, - ), - }, - ), - 6: MessageType( - name='bike_profile', - mesg_num=6, - fields={ - 0: Field( - name='name', - type=BASE_TYPES[0x07], # string - def_num=0, + }, + ), + 6: MessageType( + name='bike_profile', + mesg_num=6, + fields={ + 0: Field( + name='name', + type=BASE_TYPES[0x07], # string + def_num=0, ), 1: Field( name='sport', @@ -3124,7 +4516,7 @@ def_num=11, units='kcal', ), - 13: Field( # If New Leaf + 13: Field( name='total_fat_calories', type=BASE_TYPES[0x84], # uint16 def_num=13, @@ -5142,6 +6534,56 @@ scale=10, units='mm', ), + 91: Field( # Includes atmospheric pressure + name='absolute_pressure', + type=BASE_TYPES[0x86], # uint32 + def_num=91, + units='Pa', + ), + 92: Field( # 0 if above water + name='depth', + type=BASE_TYPES[0x86], # uint32 + def_num=92, + scale=1000, + units='m', + ), + 93: Field( # 0 if above water + name='next_stop_depth', + type=BASE_TYPES[0x86], # uint32 + def_num=93, + scale=1000, + units='m', + ), + 94: Field( + name='next_stop_time', + type=BASE_TYPES[0x86], # uint32 + def_num=94, + units='s', + ), + 95: Field( + name='time_to_surface', + type=BASE_TYPES[0x86], # uint32 + def_num=95, + units='s', + ), + 96: Field( + name='ndl_time', + type=BASE_TYPES[0x86], # uint32 + def_num=96, + units='s', + ), + 97: Field( + name='cns_load', + type=BASE_TYPES[0x02], # uint8 + def_num=97, + units='percent', + ), + 98: Field( + name='n2_load', + type=BASE_TYPES[0x84], # uint16 + def_num=98, + units='percent', + ), 253: FIELD_TYPE_TIMESTAMP, }, ), @@ -5565,7 +7007,7 @@ ), 1: Field( name='device_type', - type=FIELD_TYPES['antplus_device_type'], # uint8 + type=BASE_TYPES[0x02], # uint8 def_num=1, subfields=( SubField( @@ -5793,6 +7235,19 @@ ), ), ), + SubField( + name='duration_reps', + def_num=2, + type=BASE_TYPES[0x86], # uint32 + ref_fields=( + ReferenceField( + name='duration_type', + def_num=1, + value='reps', + raw_value=29, + ), + ), + ), SubField( # message_index of step to loop back to. Steps are assumed to be in the order by message_index. custom_name and intensity members are undefined for this duration type. name='duration_step', def_num=2, @@ -6188,6 +7643,28 @@ type=FIELD_TYPES['workout_equipment'], def_num=9, ), + 10: Field( + name='exercise_category', + type=FIELD_TYPES['exercise_category'], + def_num=10, + ), + 11: Field( + name='exercise_name', + type=BASE_TYPES[0x84], # uint16 + def_num=11, + ), + 12: Field( + name='exercise_weight', + type=BASE_TYPES[0x84], # uint16 + def_num=12, + scale=100, + units='kg', + ), + 13: Field( + name='weight_display_unit', + type=FIELD_TYPES['fit_base_unit'], + def_num=13, + ), 254: Field( name='message_index', type=FIELD_TYPES['message_index'], @@ -7339,6 +8816,8 @@ name='time256', type=BASE_TYPES[0x02], # uint8 def_num=1, + scale=256, + units='s', components=( ComponentField( name='fractional_timestamp', @@ -9126,17 +10605,505 @@ 253: FIELD_TYPE_TIMESTAMP, # Whole second part of the timestamp }, ), - - - ######################### Activity File Messages ######################### - 34: MessageType( - name='activity', - mesg_num=34, + 209: MessageType( + name='barometer_data', + mesg_num=209, fields={ - 0: Field( # Exclude pauses - name='total_timer_time', - type=BASE_TYPES[0x86], # uint32 - def_num=0, + 0: Field( # Millisecond part of the timestamp. + name='timestamp_ms', + type=BASE_TYPES[0x84], # uint16 + def_num=0, + units='ms', + ), + 1: Field( # Each time in the array describes the time at which the barometer sample with the corrosponding index was taken. The samples may span across seconds. Array size must match the number of samples in baro_cal + name='sample_time_offset', + type=BASE_TYPES[0x84], # uint16 + def_num=1, + units='ms', + ), + 2: Field( # These are the raw ADC reading. The samples may span across seconds. A conversion will need to be done on this data once read. + name='baro_pres', + type=BASE_TYPES[0x86], # uint32 + def_num=2, + units='Pa', + ), + 253: FIELD_TYPE_TIMESTAMP, # Whole second part of the timestamp + }, + ), + 210: MessageType( + name='one_d_sensor_calibration', + mesg_num=210, + fields={ + 0: Field( # Indicates which sensor the calibration is for + name='sensor_type', + type=FIELD_TYPES['sensor_type'], + def_num=0, + ), + 1: Field( # Calibration factor used to convert from raw ADC value to degrees, g, etc. + name='calibration_factor', + type=BASE_TYPES[0x86], # uint32 + def_num=1, + subfields=( + SubField( # Barometer calibration factor + name='baro_cal_factor', + def_num=1, + type=BASE_TYPES[0x86], # uint32 + units='Pa', + ref_fields=( + ReferenceField( + name='sensor_type', + def_num=0, + value='barometer', + raw_value=3, + ), + ), + ), + ), + ), + 2: Field( # Calibration factor divisor + name='calibration_divisor', + type=BASE_TYPES[0x86], # uint32 + def_num=2, + units='counts', + ), + 3: Field( # Level shift value used to shift the ADC value back into range + name='level_shift', + type=BASE_TYPES[0x86], # uint32 + def_num=3, + ), + 4: Field( # Internal Calibration factor + name='offset_cal', + type=BASE_TYPES[0x85], # sint32 + def_num=4, + ), + 253: FIELD_TYPE_TIMESTAMP, # Whole second part of the timestamp + }, + ), + 225: MessageType( + name='set', + mesg_num=225, + fields={ + 0: Field( + name='duration', + type=BASE_TYPES[0x86], # uint32 + def_num=0, + scale=1000, + units='s', + ), + 3: Field( # # of repitions of the movement + name='repetitions', + type=BASE_TYPES[0x84], # uint16 + def_num=3, + ), + 4: Field( # Amount of weight applied for the set + name='weight', + type=BASE_TYPES[0x84], # uint16 + def_num=4, + scale=16, + units='kg', + ), + 5: Field( + name='set_type', + type=FIELD_TYPES['set_type'], + def_num=5, + ), + 6: Field( # Start time of the set + name='start_time', + type=FIELD_TYPES['date_time'], + def_num=6, + ), + 7: Field( + name='category', + type=FIELD_TYPES['exercise_category'], + def_num=7, + ), + 8: Field( # Based on the associated category, see [category]_exercise_names + name='category_subtype', + type=BASE_TYPES[0x84], # uint16 + def_num=8, + ), + 9: Field( + name='weight_display_unit', + type=FIELD_TYPES['fit_base_unit'], + def_num=9, + ), + 10: Field( + name='message_index', + type=FIELD_TYPES['message_index'], + def_num=10, + ), + 11: Field( + name='wkt_step_index', + type=FIELD_TYPES['message_index'], + def_num=11, + ), + 254: Field( # Timestamp of the set + name='timestamp', + type=FIELD_TYPES['date_time'], + def_num=254, + ), + }, + ), + 227: MessageType( # Value from 1 to 100 calculated by FirstBeat + name='stress_level', + mesg_num=227, + fields={ + 0: Field( + name='stress_level_value', + type=BASE_TYPES[0x83], # sint16 + def_num=0, + ), + 1: Field( # Time stress score was calculated + name='stress_level_time', + type=FIELD_TYPES['date_time'], + def_num=1, + units='s', + ), + }, + ), + 258: MessageType( + name='dive_settings', + mesg_num=258, + fields={ + 0: Field( + name='name', + type=BASE_TYPES[0x07], # string + def_num=0, + ), + 1: Field( + name='model', + type=FIELD_TYPES['tissue_model_type'], + def_num=1, + ), + 2: Field( + name='gf_low', + type=BASE_TYPES[0x02], # uint8 + def_num=2, + units='percent', + ), + 3: Field( + name='gf_high', + type=BASE_TYPES[0x02], # uint8 + def_num=3, + units='percent', + ), + 4: Field( + name='water_type', + type=FIELD_TYPES['water_type'], + def_num=4, + ), + 5: Field( # Fresh water is usually 1000; salt water is usually 1025 + name='water_density', + type=BASE_TYPES[0x88], # float32 + def_num=5, + units='kg/m^3', + ), + 6: Field( # Typically 1.40 + name='po2_warn', + type=BASE_TYPES[0x02], # uint8 + def_num=6, + scale=100, + units='percent', + ), + 7: Field( # Typically 1.60 + name='po2_critical', + type=BASE_TYPES[0x02], # uint8 + def_num=7, + scale=100, + units='percent', + ), + 8: Field( + name='po2_deco', + type=BASE_TYPES[0x02], # uint8 + def_num=8, + scale=100, + units='percent', + ), + 9: Field( + name='safety_stop_enabled', + type=FIELD_TYPES['bool'], + def_num=9, + ), + 10: Field( + name='bottom_depth', + type=BASE_TYPES[0x88], # float32 + def_num=10, + ), + 11: Field( + name='bottom_time', + type=BASE_TYPES[0x86], # uint32 + def_num=11, + ), + 12: Field( + name='apnea_countdown_enabled', + type=FIELD_TYPES['bool'], + def_num=12, + ), + 13: Field( + name='apnea_countdown_time', + type=BASE_TYPES[0x86], # uint32 + def_num=13, + ), + 14: Field( + name='backlight_mode', + type=FIELD_TYPES['dive_backlight_mode'], + def_num=14, + ), + 15: Field( + name='backlight_brightness', + type=BASE_TYPES[0x02], # uint8 + def_num=15, + ), + 16: Field( + name='backlight_timeout', + type=FIELD_TYPES['backlight_timeout'], + def_num=16, + ), + 17: Field( # Time between surfacing and ending the activity + name='repeat_dive_interval', + type=BASE_TYPES[0x84], # uint16 + def_num=17, + units='s', + ), + 18: Field( # Time at safety stop (if enabled) + name='safety_stop_time', + type=BASE_TYPES[0x84], # uint16 + def_num=18, + units='s', + ), + 19: Field( + name='heart_rate_source_type', + type=FIELD_TYPES['source_type'], + def_num=19, + ), + 20: Field( + name='heart_rate_source', + type=BASE_TYPES[0x02], # uint8 + def_num=20, + subfields=( + SubField( + name='heart_rate_antplus_device_type', + def_num=20, + type=FIELD_TYPES['antplus_device_type'], + ref_fields=( + ReferenceField( + name='heart_rate_source_type', + def_num=19, + value='antplus', + raw_value=1, + ), + ), + ), + SubField( + name='heart_rate_local_device_type', + def_num=20, + type=FIELD_TYPES['local_device_type'], + ref_fields=( + ReferenceField( + name='heart_rate_source_type', + def_num=19, + value='local', + raw_value=5, + ), + ), + ), + ), + ), + 254: Field( + name='message_index', + type=FIELD_TYPES['message_index'], + def_num=254, + ), + }, + ), + 259: MessageType( + name='dive_gas', + mesg_num=259, + fields={ + 0: Field( + name='helium_content', + type=BASE_TYPES[0x02], # uint8 + def_num=0, + units='percent', + ), + 1: Field( + name='oxygen_content', + type=BASE_TYPES[0x02], # uint8 + def_num=1, + units='percent', + ), + 2: Field( + name='status', + type=FIELD_TYPES['dive_gas_status'], + def_num=2, + ), + 254: Field( + name='message_index', + type=FIELD_TYPES['message_index'], + def_num=254, + ), + }, + ), + 262: MessageType( + name='dive_alarm', + mesg_num=262, + fields={ + 0: Field( + name='depth', + type=BASE_TYPES[0x86], # uint32 + def_num=0, + scale=1000, + units='m', + ), + 1: Field( + name='time', + type=BASE_TYPES[0x85], # sint32 + def_num=1, + units='s', + ), + 2: Field( + name='enabled', + type=FIELD_TYPES['bool'], + def_num=2, + ), + 3: Field( + name='alarm_type', + type=FIELD_TYPES['dive_alarm_type'], + def_num=3, + ), + 4: Field( + name='sound', + type=FIELD_TYPES['tone'], + def_num=4, + ), + 5: Field( + name='dive_types', + type=FIELD_TYPES['sub_sport'], + def_num=5, + ), + 254: Field( # Index of the alarm + name='message_index', + type=FIELD_TYPES['message_index'], + def_num=254, + ), + }, + ), + 264: MessageType( + name='exercise_title', + mesg_num=264, + fields={ + 0: Field( + name='exercise_category', + type=FIELD_TYPES['exercise_category'], + def_num=0, + ), + 1: Field( + name='exercise_name', + type=BASE_TYPES[0x84], # uint16 + def_num=1, + ), + 2: Field( + name='wkt_step_name', + type=BASE_TYPES[0x07], # string + def_num=2, + ), + 254: Field( + name='message_index', + type=FIELD_TYPES['message_index'], + def_num=254, + ), + }, + ), + 268: MessageType( + name='dive_summary', + mesg_num=268, + fields={ + 0: Field( + name='reference_mesg', + type=FIELD_TYPES['mesg_num'], + def_num=0, + ), + 1: Field( + name='reference_index', + type=FIELD_TYPES['message_index'], + def_num=1, + ), + 2: Field( # 0 if above water + name='avg_depth', + type=BASE_TYPES[0x86], # uint32 + def_num=2, + scale=1000, + units='m', + ), + 3: Field( # 0 if above water + name='max_depth', + type=BASE_TYPES[0x86], # uint32 + def_num=3, + scale=1000, + units='m', + ), + 4: Field( # Time since end of last dive + name='surface_interval', + type=BASE_TYPES[0x86], # uint32 + def_num=4, + units='s', + ), + 5: Field( + name='start_cns', + type=BASE_TYPES[0x02], # uint8 + def_num=5, + units='percent', + ), + 6: Field( + name='end_cns', + type=BASE_TYPES[0x02], # uint8 + def_num=6, + units='percent', + ), + 7: Field( + name='start_n2', + type=BASE_TYPES[0x84], # uint16 + def_num=7, + units='percent', + ), + 8: Field( + name='end_n2', + type=BASE_TYPES[0x84], # uint16 + def_num=8, + units='percent', + ), + 9: Field( + name='o2_toxicity', + type=BASE_TYPES[0x84], # uint16 + def_num=9, + units='OTUs', + ), + 10: Field( + name='dive_number', + type=BASE_TYPES[0x86], # uint32 + def_num=10, + ), + 11: Field( + name='bottom_time', + type=BASE_TYPES[0x86], # uint32 + def_num=11, + scale=1000, + units='s', + ), + 253: FIELD_TYPE_TIMESTAMP, + }, + ), + + + # ************************* Activity File Messages ************************* + 34: MessageType( + name='activity', + mesg_num=34, + fields={ + 0: Field( # Exclude pauses + name='total_timer_time', + type=BASE_TYPES[0x86], # uint32 + def_num=0, scale=1000, units='s', ), @@ -9175,7 +11142,7 @@ ), - ###################### Blood Pressure File Messages ###################### + # ********************** Blood Pressure File Messages ********************** 51: MessageType( name='blood_pressure', mesg_num=51, @@ -9242,7 +11209,7 @@ ), - ########################## Course File Messages ########################## + # ************************** Course File Messages ************************** 31: MessageType( name='course', mesg_num=31, @@ -9271,7 +11238,7 @@ ), - ########################## Device File Messages ########################## + # ************************** Device File Messages ************************** 35: MessageType( name='software', mesg_num=35, @@ -9296,7 +11263,7 @@ ), - ########################## Goals File Messages ########################### + # ************************** Goals File Messages *************************** 15: MessageType( name='goal', mesg_num=15, @@ -9370,7 +11337,7 @@ ), - ######################## Monitoring File Messages ######################## + # ************************ Monitoring File Messages ************************ 103: MessageType( name='monitoring_info', mesg_num=103, @@ -9411,7 +11378,7 @@ ), - ############################# Other Messages ############################# + # ***************************** Other Messages ***************************** 145: MessageType( name='memo_glob', mesg_num=145, @@ -9440,7 +11407,7 @@ ), - ######################### Schedule File Messages ######################### + # ************************* Schedule File Messages ************************* 28: MessageType( name='schedule', mesg_num=28, @@ -9511,7 +11478,7 @@ ), - ######################### Segment File Messages ########################## + # ************************* Segment File Messages ************************** 148: MessageType( # Unique Identification data for a segment file name='segment_id', mesg_num=148, @@ -9565,7 +11532,7 @@ ), - ####################### Segment List File Messages ####################### + # *********************** Segment List File Messages *********************** 151: MessageType( # Summary of the unique segment and leaderboard information associated with a segment file. This message is used to compile a segment list file describing all segment files on a device. The segment list file is used when refreshing the contents of a segment file with the latest available leaderboard information. name='segment_file', mesg_num=151, @@ -9619,7 +11586,7 @@ ), - ######################### Settings File Messages ######################### + # ************************* Settings File Messages ************************* 2: MessageType( name='device_settings', mesg_num=2, @@ -9739,11 +11706,180 @@ type=FIELD_TYPES['display_orientation'], def_num=95, ), + 134: Field( + name='tap_interface', + type=FIELD_TYPES['switch'], + def_num=134, + ), + }, + ), + 3: MessageType( + name='user_profile', + mesg_num=3, + fields={ + 0: Field( + name='friendly_name', + type=BASE_TYPES[0x07], # string + def_num=0, + ), + 1: Field( + name='gender', + type=FIELD_TYPES['gender'], + def_num=1, + ), + 2: Field( + name='age', + type=BASE_TYPES[0x02], # uint8 + def_num=2, + units='years', + ), + 3: Field( + name='height', + type=BASE_TYPES[0x02], # uint8 + def_num=3, + scale=100, + units='m', + ), + 4: Field( + name='weight', + type=BASE_TYPES[0x84], # uint16 + def_num=4, + scale=10, + units='kg', + ), + 5: Field( + name='language', + type=FIELD_TYPES['language'], + def_num=5, + ), + 6: Field( + name='elev_setting', + type=FIELD_TYPES['display_measure'], + def_num=6, + ), + 7: Field( + name='weight_setting', + type=FIELD_TYPES['display_measure'], + def_num=7, + ), + 8: Field( + name='resting_heart_rate', + type=BASE_TYPES[0x02], # uint8 + def_num=8, + units='bpm', + ), + 9: Field( + name='default_max_running_heart_rate', + type=BASE_TYPES[0x02], # uint8 + def_num=9, + units='bpm', + ), + 10: Field( + name='default_max_biking_heart_rate', + type=BASE_TYPES[0x02], # uint8 + def_num=10, + units='bpm', + ), + 11: Field( + name='default_max_heart_rate', + type=BASE_TYPES[0x02], # uint8 + def_num=11, + units='bpm', + ), + 12: Field( + name='hr_setting', + type=FIELD_TYPES['display_heart'], + def_num=12, + ), + 13: Field( + name='speed_setting', + type=FIELD_TYPES['display_measure'], + def_num=13, + ), + 14: Field( + name='dist_setting', + type=FIELD_TYPES['display_measure'], + def_num=14, + ), + 16: Field( + name='power_setting', + type=FIELD_TYPES['display_power'], + def_num=16, + ), + 17: Field( + name='activity_class', + type=FIELD_TYPES['activity_class'], + def_num=17, + ), + 18: Field( + name='position_setting', + type=FIELD_TYPES['display_position'], + def_num=18, + ), + 21: Field( + name='temperature_setting', + type=FIELD_TYPES['display_measure'], + def_num=21, + ), + 22: Field( + name='local_id', + type=FIELD_TYPES['user_local_id'], + def_num=22, + ), + 23: Field( + name='global_id', + type=BASE_TYPES[0x0D], # byte + def_num=23, + ), + 28: Field( # Typical wake time + name='wake_time', + type=FIELD_TYPES['localtime_into_day'], + def_num=28, + ), + 29: Field( # Typical bed time + name='sleep_time', + type=FIELD_TYPES['localtime_into_day'], + def_num=29, + ), + 30: Field( + name='height_setting', + type=FIELD_TYPES['display_measure'], + def_num=30, + ), + 31: Field( # User defined running step length set to 0 for auto length + name='user_running_step_length', + type=BASE_TYPES[0x84], # uint16 + def_num=31, + scale=1000, + units='m', + ), + 32: Field( # User defined walking step length set to 0 for auto length + name='user_walking_step_length', + type=BASE_TYPES[0x84], # uint16 + def_num=32, + scale=1000, + units='m', + ), + 47: Field( + name='depth_setting', + type=FIELD_TYPES['display_measure'], + def_num=47, + ), + 49: Field( + name='dive_count', + type=BASE_TYPES[0x86], # uint32 + def_num=49, + ), + 254: Field( + name='message_index', + type=FIELD_TYPES['message_index'], + def_num=254, + ), }, ), - ###################### Sport Settings File Messages ###################### + # ********************** Sport Settings File Messages ********************** 7: MessageType( name='zones_target', mesg_num=7, @@ -9777,7 +11913,7 @@ ), - ########################## Totals File Messages ########################## + # ************************** Totals File Messages ************************** 33: MessageType( name='totals', mesg_num=33, @@ -9837,7 +11973,7 @@ ), - ####################### Weight Scale File Messages ####################### + # *********************** Weight Scale File Messages *********************** 30: MessageType( name='weight_scale', mesg_num=30, @@ -9924,7 +12060,7 @@ ), - ######################### Workout File Messages ########################## + # ************************* Workout File Messages ************************** 26: MessageType( name='workout', mesg_num=26, diff --git a/fitparse/records.py b/fitparse/records.py index 71edc93..d007f0a 100644 --- a/fitparse/records.py +++ b/fitparse/records.py @@ -299,7 +299,7 @@ class SubField(FieldAndSubFieldBase): class DevField(FieldAndSubFieldBase): __slots__ = ('dev_data_index', 'def_num', 'type', 'name', 'units', 'native_field_num', # The rest of these are just to be compatible with Field objects. They're always None - 'scale', 'offset', 'components', 'subfields') + 'scale', 'offset', 'components', 'subfields') field_type = 'devfield' @@ -318,6 +318,14 @@ def render(self, raw_value): # If it's a tuple, then it's a byte array and unpack it as such # (only type that uses this is compressed speed/distance) if isinstance(raw_value, tuple): + # Profile.xls sometimes contains more components than the read raw + # value is able to hold (typically the *event_timestamp_12* field in + # *hr* messages). + # This test allows to ensure *unpacked_num* is not right-shifted + # more than necessary. + if self.bit_offset and self.bit_offset >= len(raw_value) << 3: + raise ValueError() + unpacked_num = 0 # Unpack byte array as little endian @@ -381,11 +389,23 @@ def calculate(cls, byte_arr, crc=0): def parse_string(string): try: - end = string.index(0x00) - except TypeError: # Python 2 compat - end = string.index('\x00') - - return string[:end].decode('utf-8', errors='replace') or None + try: + s = string[:string.index(0x00)] + except TypeError: # Python 2 compat + s = string[:string.index('\x00')] + except ValueError: + # FIT specification defines the 'string' type as follows: "Null + # terminated string encoded in UTF-8 format". + # + # However 'string' values are not always null-terminated when encoded, + # according to FIT files created by Garmin devices (e.g. DEVICE.FIT file + # from a fenix3). + # + # So in order to be more flexible, in case index() could not find any + # null byte, we just decode the whole bytes-like object. + s = string + + return s.decode(encoding='utf-8', errors='replace') or None # The default base type BASE_TYPE_BYTE = BaseType(name='byte', identifier=0x0D, fmt='B', parse=lambda x: None if all(b == 0xFF for b in x) else x) @@ -405,6 +425,9 @@ def parse_string(string): 0x8B: BaseType(name='uint16z', identifier=0x8B, fmt='H', parse=lambda x: None if x == 0x0 else x), 0x8C: BaseType(name='uint32z', identifier=0x8C, fmt='I', parse=lambda x: None if x == 0x0 else x), 0x0D: BASE_TYPE_BYTE, + 0x8E: BaseType(name='sint64', identifier=0x8E, fmt='q', parse=lambda x: None if x == 0x7FFFFFFFFFFFFFFF else x), + 0x8F: BaseType(name='uint64', identifier=0x8F, fmt='Q', parse=lambda x: None if x == 0xFFFFFFFFFFFFFFFF else x), + 0x90: BaseType(name='uint64z', identifier=0x90, fmt='Q', parse=lambda x: None if x == 0 else x), } diff --git a/fitparse/utils.py b/fitparse/utils.py index d3e427a..d3e4e02 100644 --- a/fitparse/utils.py +++ b/fitparse/utils.py @@ -1,6 +1,9 @@ import io import re -from collections import Iterable +try: + from collections.abc import Iterable +except ImportError: + from collections import Iterable class FitParseError(ValueError): diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 0000000..b6627b4 --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,12 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# pip-compile --output-file requirements-test.txt etc/requirements-test.in +# + +click==7.0 # via pip-tools +coverage==4.5.2 +pip-tools==3.2.0 +six==1.12.0 # via pip-tools +coveralls==1.7.0 # via pip-tools diff --git a/scripts/generate_profile.py b/scripts/generate_profile.py index 52e5c00..d68c816 100755 --- a/scripts/generate_profile.py +++ b/scripts/generate_profile.py @@ -20,11 +20,19 @@ import xlrd # Dev requirement for parsing Excel spreadsheet +FIELD_NUM_TIMESTAMP = 253 + XLS_HEADER_MAGIC = b'\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1' +SYMBOL_NAME_SCRUBBER = re.compile(r'\W|^(?=\d)') + def header(header, indent=0): - return '%s%s' % (' ' * indent, (' %s ' % header).center(78 - indent, '#')) + return '%s# %s' % (' ' * indent, (' %s ' % header).center(78 - indent, '*')) + + +def scrub_symbol_name(symbol_name): + return SYMBOL_NAME_SCRUBBER.sub('_', symbol_name) PROFILE_HEADER_FIRST_PART = "%s\n%s" % ( @@ -42,7 +50,17 @@ def header(header, indent=0): BASE_TYPES, )''' -SPECIAL_FIELD_DECLARTIONS = "FIELD_TYPE_TIMESTAMP = Field(name='timestamp', type=FIELD_TYPES['date_time'], def_num=253, units='s')" +# This allows to prepend the declaration of some message numbers to the +# generated file. +# E.g. 'hr' -> MESG_NUM_HR = 132 +MESSAGE_NUM_DECLARATIONS = () + +# This allows to prepend the declaration of some field numbers of specific +# messages to the generated file. +# E.g. 'hr.event_timestamp' -> FIELD_NUM_HR_EVENT_TIMESTAMP = 9 +FIELD_NUM_DECLARATIONS = () + +SPECIAL_FIELD_DECLARATIONS = "FIELD_TYPE_TIMESTAMP = Field(name='timestamp', type=FIELD_TYPES['date_time'], def_num=" + str(FIELD_NUM_TIMESTAMP) + ", units='s')" IGNORE_TYPE_VALUES = ( # of the form 'type_name:value_name' @@ -52,10 +70,23 @@ def header(header, indent=0): ) BASE_TYPES = { - 'enum': '0x00', 'sint8': '0x01', 'uint8': '0x02', 'sint16': '0x83', - 'uint16': '0x84', 'sint32': '0x85', 'uint32': '0x86', 'string': '0x07', - 'float32': '0x88', 'float64': '0x89', 'uint8z': '0x0A', 'uint16z': '0x8B', - 'uint32z': '0x8C', 'byte': '0x0D', + 'enum': '0x00', + 'sint8': '0x01', + 'uint8': '0x02', + 'sint16': '0x83', + 'uint16': '0x84', + 'sint32': '0x85', + 'uint32': '0x86', + 'string': '0x07', + 'float32': '0x88', + 'float64': '0x89', + 'uint8z': '0x0A', + 'uint16z': '0x8B', + 'uint32z': '0x8C', + 'byte': '0x0D', + 'sint64': '0x8E', + 'uint64': '0x8F', + 'uint64z': '0x90', } @@ -143,6 +174,22 @@ def __str__(self): s += '}' return s + def get_by_name(self, mesg_name): + for mesg in self.messages: + if mesg.name == mesg_name: + return mesg + + raise ValueError('message "%s" not found' % mesg_name) + + def get_field_by_name(self, mesg_name, field_name): + mesg = self.get_by_name(mesg_name) + + for field in mesg.fields: + if field.name == field_name: + return mesg, field + + raise ValueError('field "%s" not found in message "%s"' % (field_name, mesg_name)) + class MessageInfo(namedtuple('MessageInfo', ('name', 'num', 'group_name', 'fields', 'comment'))): def get(self, field_name): @@ -166,7 +213,7 @@ def __str__(self): class FieldInfo(namedtuple('FieldInfo', ('name', 'type', 'num', 'scale', 'offset', 'units', 'components', 'subfields', 'comment'))): def __str__(self): - if self.num == 253: + if self.num == FIELD_NUM_TIMESTAMP: # Add trailing comma here because of comment assert not self.components and not self.subfields return 'FIELD_TYPE_TIMESTAMP,%s' % render_comment(self.comment) @@ -359,6 +406,7 @@ def maybe_decode(o): return o.decode() return o + def parse_messages(messages_rows, type_list): message_list = MessageList([]) @@ -389,10 +437,10 @@ def parse_messages(messages_rows, type_list): ) for cmp_name, cmp_scale, cmp_offset, cmp_units, cmp_bits, cmp_accumulate in zip( component_names, # name - parse_csv_fields(maybe_decode(row[6]), num_components), # scale - parse_csv_fields(maybe_decode(row[7]), num_components), # offset - parse_csv_fields(maybe_decode(row[8]), num_components), # units - parse_csv_fields(maybe_decode(row[9]), num_components), # bits + parse_csv_fields(maybe_decode(row[6]), num_components), # scale + parse_csv_fields(maybe_decode(row[7]), num_components), # offset + parse_csv_fields(maybe_decode(row[8]), num_components), # units + parse_csv_fields(maybe_decode(row[9]), num_components), # bits parse_csv_fields(maybe_decode(row[10]), num_components), # accumulate ) ] @@ -402,12 +450,12 @@ def parse_messages(messages_rows, type_list): assert component.name assert component.bits - # Otherwise a field + # Otherwise a field # Not a subfield if first row has definition num if row[1] is not None and row[1] != b'': field = FieldInfo( name=row[2].decode(), type=row[3].decode(), num=maybe_decode(row[1]), scale=fix_scale(row[6]), - offset=row[7], units=fix_units(row[8].decode()), components=[], + offset=maybe_decode(row[7]), units=fix_units(row[8].decode()), components=[], subfields=[], comment=row[13].decode(), ) @@ -417,15 +465,17 @@ def parse_messages(messages_rows, type_list): # Add components if they exist if components: field.components.extend(components) - # Wipe out scale, units, offset from field since it's a component - field = field._replace(scale=None, offset=None, units=None) + + # Wipe out scale, units, offset from field since components scale is None or b'' or is not digit + if row[6] is None or row[6] == b'' or not str(row[6]).isdigit(): + field = field._replace(scale=None, offset=None, units=None) message.fields.append(field) elif row[2] != b'': # Sub fields subfield = SubFieldInfo( name=row[2].decode(), num=field.num, type=row[3].decode(), scale=fix_scale(row[6]), - offset=row[7], units=fix_units(row[8].decode()), ref_fields=[], + offset=maybe_decode(row[7]), units=fix_units(row[8].decode()), ref_fields=[], components=[], comment=row[13].decode(), ) @@ -447,7 +497,7 @@ def parse_messages(messages_rows, type_list): ) assert len(subfield.ref_fields) == len(ref_field_names) - if not "alert_type" in ref_field_names: + if "alert_type" not in ref_field_names: field.subfields.append(subfield) # Resolve reference fields for subfields and components @@ -483,15 +533,15 @@ def get_xls_and_version_from_zip(path): version_match = re.search( r'Profile Version.+?(\d+\.?\d*).*', - archive.open('c/fit.h').read().decode(), + archive.open('FitSDKRelease_21.27.00/c/fit.h').read().decode(), ) if version_match: profile_version = ("%f" % float(version_match.group(1))).rstrip('0').ljust(4, '0') try: - return archive.open('Profile.xls'), profile_version + return archive.open('FitSDKRelease_21.27.00/Profile.xls'), profile_version except KeyError: - return archive.open('Profile.xlsx'), profile_version + return archive.open('FitSDKRelease_21.27.00/Profile.xlsx'), profile_version def main(input_xls_or_zip, output_py_path=None): @@ -509,19 +559,48 @@ def main(input_xls_or_zip, output_py_path=None): type_list = parse_types(types_rows) message_list = parse_messages(messages_rows, type_list) + mesg_num_declarations = [] + for mesg_name in MESSAGE_NUM_DECLARATIONS: + mesg_info = message_list.get_by_name(mesg_name) + + mesg_num_declarations.append('MESG_NUM_%s = %s' % ( + scrub_symbol_name(mesg_name).upper(), + str(mesg_info.num) if mesg_info else 'None')) + + field_num_declarations = [ + 'FIELD_NUM_TIMESTAMP = ' + str(FIELD_NUM_TIMESTAMP)] + for field_fqn in FIELD_NUM_DECLARATIONS: + mesg_name, field_name = field_fqn.split('.', maxsplit=1) + mesg_info, field_info = message_list.get_field_by_name(mesg_name, field_name) + + field_decl = 'FIELD_NUM_%s_%s = %s' % ( + scrub_symbol_name(mesg_name).upper(), + scrub_symbol_name(field_name).upper(), + str(field_info.num)) + + field_num_declarations.append(field_decl) + output = '\n'.join([ "\n%s" % PROFILE_HEADER_FIRST_PART, - header('EXPORTED PROFILE FROM %s AT %s' % ( + header('EXPORTED PROFILE FROM %s ON %s' % ( ('SDK VERSION %s' % profile_version) if profile_version else 'SPREADSHEET', - datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + datetime.datetime.now().strftime('%Y-%m-%d'), )), header('PARSED %d TYPES (%d VALUES), %d MESSAGES (%d FIELDS)' % ( len(type_list.types), sum(len(ti.values) for ti in type_list.types), len(message_list.messages), sum(len(mi.fields) for mi in message_list.messages), )), - '', IMPORT_HEADER, '\n', + '', IMPORT_HEADER + ]) + '\n' + + if mesg_num_declarations: + output += '\n\n' + '\n'.join(mesg_num_declarations) + '\n' + if field_num_declarations: + output += '\n\n' + '\n'.join(field_num_declarations) + '\n' + + output += '\n\n' + '\n'.join([ str(type_list), '\n', - SPECIAL_FIELD_DECLARTIONS, '\n', + SPECIAL_FIELD_DECLARATIONS, '\n', str(message_list), '' ]) @@ -530,17 +609,16 @@ def main(input_xls_or_zip, output_py_path=None): if output_py_path: open(output_py_path, 'w').write(output) - print("Profile%s written to %s", - ' version %s' % profile_version if profile_version else '', - output_py_path - ) + print('Profile version %s written to %s' % ( + profile_version if profile_version else '', + output_py_path)) else: print(output.strip()) if __name__ == '__main__': if len(sys.argv) < 2: - print("Usage: %s [profile.py]", os.path.basename(__file__)) + print("Usage: %s [profile.py]" % os.path.basename(__file__)) sys.exit(0) xls = sys.argv[1]