diff --git a/garmin_fit_sdk/__init__.py b/garmin_fit_sdk/__init__.py index 52ce2f7..b16b29c 100644 --- a/garmin_fit_sdk/__init__.py +++ b/garmin_fit_sdk/__init__.py @@ -7,8 +7,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.178.0Release -# Tag = production/release/21.178.0-0-g3bea629 +# Profile Version = 21.188.0Release +# Tag = production/release/21.188.0-0-g55050f8 ############################################################################################ @@ -22,4 +22,4 @@ from garmin_fit_sdk.stream import Stream from garmin_fit_sdk.util import FIT_EPOCH_S, convert_timestamp_to_datetime -__version__ = '21.178.0' +__version__ = '21.188.0' diff --git a/garmin_fit_sdk/accumulator.py b/garmin_fit_sdk/accumulator.py index ac2b5ff..8a64be3 100644 --- a/garmin_fit_sdk/accumulator.py +++ b/garmin_fit_sdk/accumulator.py @@ -7,8 +7,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.178.0Release -# Tag = production/release/21.178.0-0-g3bea629 +# Profile Version = 21.188.0Release +# Tag = production/release/21.188.0-0-g55050f8 ############################################################################################ diff --git a/garmin_fit_sdk/bitstream.py b/garmin_fit_sdk/bitstream.py index 568af31..381e62c 100644 --- a/garmin_fit_sdk/bitstream.py +++ b/garmin_fit_sdk/bitstream.py @@ -7,8 +7,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.178.0Release -# Tag = production/release/21.178.0-0-g3bea629 +# Profile Version = 21.188.0Release +# Tag = production/release/21.188.0-0-g55050f8 ############################################################################################ diff --git a/garmin_fit_sdk/crc_calculator.py b/garmin_fit_sdk/crc_calculator.py index 6a33828..e5fcec0 100644 --- a/garmin_fit_sdk/crc_calculator.py +++ b/garmin_fit_sdk/crc_calculator.py @@ -7,8 +7,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.178.0Release -# Tag = production/release/21.178.0-0-g3bea629 +# Profile Version = 21.188.0Release +# Tag = production/release/21.188.0-0-g55050f8 ############################################################################################ diff --git a/garmin_fit_sdk/decoder.py b/garmin_fit_sdk/decoder.py index b45442f..b1c7a80 100644 --- a/garmin_fit_sdk/decoder.py +++ b/garmin_fit_sdk/decoder.py @@ -7,8 +7,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.178.0Release -# Tag = production/release/21.178.0-0-g3bea629 +# Profile Version = 21.188.0Release +# Tag = production/release/21.188.0-0-g55050f8 ############################################################################################ @@ -217,7 +217,7 @@ def __decode_mesg_def(self): field_definition = { "field_id": self._stream.read_byte(), "size": self._stream.read_byte(), - "base_type": self._stream.read_byte(), + "base_type": self._stream.read_byte() & FIT.BASE_TYPE_MASK, } if field_definition["base_type"] not in FIT.BASE_TYPE_DEFINITIONS: @@ -636,14 +636,15 @@ def __add_field_description_to_profile(self, message): return if message["fit_base_type_id"] is not None: - base_type_code = FIT.BASE_TYPE_DEFINITIONS[message["fit_base_type_id"]['raw_field_value']]["type_code"] + masked_base_type = message["fit_base_type_id"]['raw_field_value'] & FIT.BASE_TYPE_MASK + base_type_code = FIT.BASE_TYPE_DEFINITIONS[masked_base_type]["type_code"] else: base_type_code = None self._developer_data_defs[message['developer_data_index']['raw_field_value']]['fields'].append({ 'developer_data_index': message['developer_data_index']['raw_field_value'], 'field_definition_number': message['field_definition_number']['raw_field_value'], - 'fit_base_type_id': message['fit_base_type_id']['raw_field_value'] if 'fit_base_type_id' in message else None, + 'fit_base_type_id': message['fit_base_type_id']['raw_field_value'] & FIT.BASE_TYPE_MASK if 'fit_base_type_id' in message else None, 'base_type_code': base_type_code, 'name': message['name']['raw_field_value'] if 'name' in message else None, 'array': message['array']['raw_field_value'] if 'array' in message else None, diff --git a/garmin_fit_sdk/fit.py b/garmin_fit_sdk/fit.py index ecf9efd..2d82f82 100644 --- a/garmin_fit_sdk/fit.py +++ b/garmin_fit_sdk/fit.py @@ -7,8 +7,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.178.0Release -# Tag = production/release/21.178.0-0-g3bea629 +# Profile Version = 21.188.0Release +# Tag = production/release/21.188.0-0-g55050f8 ############################################################################################ @@ -16,20 +16,20 @@ "ENUM": 0x00, "SINT8": 0x01, "UINT8": 0x02, - "SINT16": 0x83, - "UINT16": 0x84, - "SINT32": 0x85, - "UINT32": 0x86, + "SINT16": 0x03, + "UINT16": 0x04, + "SINT32": 0x05, + "UINT32": 0x06, "STRING": 0x07, - "FLOAT32": 0x88, - "FLOAT64": 0x89, + "FLOAT32": 0x08, + "FLOAT64": 0x09, "UINT8Z": 0x0A, - "UINT16Z": 0x8B, - "UINT32Z": 0x8C, + "UINT16Z": 0x0B, + "UINT32Z": 0x0C, "BYTE": 0x0D, - "SINT64": 0x8E, - "UINT64": 0x8F, - "UINT64Z": 0x90 + "SINT64": 0x0E, + "UINT64": 0x0F, + "UINT64Z": 0x10 } FIELD_TYPE_TO_BASE_TYPE = { @@ -55,22 +55,24 @@ 0x00: {'size': 1, 'type': BASE_TYPE["ENUM"], 'signed': False, 'type_code': 'B', 'invalid': 0xFF}, 0x01: {'size': 1, 'type': BASE_TYPE["SINT8"], 'signed': True, 'type_code': 'b', 'invalid': 0x7F}, 0x02: {'size': 1, 'type': BASE_TYPE["UINT8"], 'signed': False, 'type_code': 'B', 'invalid': 0xFF}, - 0x83: {'size': 2, 'type': BASE_TYPE["SINT16"], 'signed': True, 'type_code': 'h', 'invalid': 0x7FFF}, - 0x84: {'size': 2, 'type': BASE_TYPE["UINT16"], 'signed': False, 'type_code': 'H', 'invalid': 0xFFFF}, - 0x85: {'size': 4, 'type': BASE_TYPE["SINT32"], 'signed': True, 'type_code': 'i', 'invalid': 0x7FFFFFFF}, - 0x86: {'size': 4, 'type': BASE_TYPE["UINT32"], 'signed': False, 'type_code': 'I', 'invalid': 0xFFFFFFFF}, + 0x03: {'size': 2, 'type': BASE_TYPE["SINT16"], 'signed': True, 'type_code': 'h', 'invalid': 0x7FFF}, + 0x04: {'size': 2, 'type': BASE_TYPE["UINT16"], 'signed': False, 'type_code': 'H', 'invalid': 0xFFFF}, + 0x05: {'size': 4, 'type': BASE_TYPE["SINT32"], 'signed': True, 'type_code': 'i', 'invalid': 0x7FFFFFFF}, + 0x06: {'size': 4, 'type': BASE_TYPE["UINT32"], 'signed': False, 'type_code': 'I', 'invalid': 0xFFFFFFFF}, 0x07: {'size': 1, 'type': BASE_TYPE["STRING"], 'signed': False, 'type_code': 's', 'invalid': 0x00}, - 0x88: {'size': 4, 'type': BASE_TYPE["FLOAT32"], 'signed': True, 'type_code': 'f', 'invalid': 0xFFFFFFFF}, - 0x89: {'size': 8, 'type': BASE_TYPE["FLOAT64"], 'signed': True, 'type_code': 'd', 'invalid': 0xFFFFFFFFFFFFFFFF}, + 0x08: {'size': 4, 'type': BASE_TYPE["FLOAT32"], 'signed': True, 'type_code': 'f', 'invalid': 0xFFFFFFFF}, + 0x09: {'size': 8, 'type': BASE_TYPE["FLOAT64"], 'signed': True, 'type_code': 'd', 'invalid': 0xFFFFFFFFFFFFFFFF}, 0x0A: {'size': 1, 'type': BASE_TYPE["UINT8Z"], 'signed': False, 'type_code': 'B', 'invalid': 0x00}, - 0x8B: {'size': 2, 'type': BASE_TYPE["UINT16Z"], 'signed': False, 'type_code': 'H', 'invalid': 0x0000}, - 0x8C: {'size': 4, 'type': BASE_TYPE["UINT32Z"], 'signed': False, 'type_code': 'I', 'invalid': 0x00000000}, + 0x0B: {'size': 2, 'type': BASE_TYPE["UINT16Z"], 'signed': False, 'type_code': 'H', 'invalid': 0x0000}, + 0x0C: {'size': 4, 'type': BASE_TYPE["UINT32Z"], 'signed': False, 'type_code': 'I', 'invalid': 0x00000000}, 0x0D: {'size': 1, 'type': BASE_TYPE["BYTE"], 'signed': False, 'type_code': 'B', 'invalid': 0xFF}, - 0x8E: {'size': 8, 'type': BASE_TYPE["SINT64"], 'signed': True, 'type_code': 'q', 'invalid': 0x7FFFFFFFFFFFFFFF}, - 0x8F: {'size': 8, 'type': BASE_TYPE["UINT64"], 'signed': False, 'type_code': 'Q', 'invalid': 0xFFFFFFFFFFFFFFFF}, - 0x90: {'size': 8, 'type': BASE_TYPE["UINT64Z"], 'signed': False, 'type_code': 'L', 'invalid': 0x0000000000000000}, + 0x0E: {'size': 8, 'type': BASE_TYPE["SINT64"], 'signed': True, 'type_code': 'q', 'invalid': 0x7FFFFFFFFFFFFFFF}, + 0x0F: {'size': 8, 'type': BASE_TYPE["UINT64"], 'signed': False, 'type_code': 'Q', 'invalid': 0xFFFFFFFFFFFFFFFF}, + 0x10: {'size': 8, 'type': BASE_TYPE["UINT64Z"], 'signed': False, 'type_code': 'L', 'invalid': 0x0000000000000000}, } +BASE_TYPE_MASK = 0x1F + NUMERIC_FIELD_TYPES = [ "sint8", "uint8", diff --git a/garmin_fit_sdk/hr_mesg_utils.py b/garmin_fit_sdk/hr_mesg_utils.py index 7b42263..dc8662d 100644 --- a/garmin_fit_sdk/hr_mesg_utils.py +++ b/garmin_fit_sdk/hr_mesg_utils.py @@ -7,8 +7,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.178.0Release -# Tag = production/release/21.178.0-0-g3bea629 +# Profile Version = 21.188.0Release +# Tag = production/release/21.188.0-0-g55050f8 ############################################################################################ diff --git a/garmin_fit_sdk/profile.py b/garmin_fit_sdk/profile.py index a840e8a..2582364 100644 --- a/garmin_fit_sdk/profile.py +++ b/garmin_fit_sdk/profile.py @@ -5,15 +5,15 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.178.0Release -# Tag = production/release/21.178.0-0-g3bea629 +# Profile Version = 21.188.0Release +# Tag = production/release/21.188.0-0-g55050f8 ############################################################################################ Profile = { 'version': { 'major': 21, - 'minor': 178, + 'minor': 188, 'patch': 0, 'type': "Release" }, @@ -4473,7 +4473,7 @@ 'sub_fields': [] }, 253: { - 'num': 253, # Sesson end time. + 'num': 253, 'name': "timestamp", 'type': "date_time", 'array': "false", @@ -6724,7 +6724,7 @@ 'sub_fields': [] }, 253: { - 'num': 253, # Lap end time. + 'num': 253, 'name': "timestamp", 'type': "date_time", 'array': "false", @@ -14806,7 +14806,7 @@ 'sub_fields': [] }, 253: { - 'num': 253, # Lap end time. + 'num': 253, 'name': "timestamp", 'type': "date_time", 'array': "false", @@ -23405,6 +23405,7 @@ '4446': 'hrm_fit', '4472': 'marq_gen2_commander', '4477': 'lily_athlete', # aka the Lily 2 Active + '4525': 'rally_x10', # Rally 110/210 '4532': 'fenix8_solar', '4533': 'fenix8_solar_large', '4534': 'fenix8_small', @@ -23420,13 +23421,22 @@ '4603': 'venu_x1', '4606': 'hrm_200', '4625': 'vivoactive6', + '4631': 'fenix8_pro', + '4633': 'edge_550', + '4634': 'edge_850', + '4643': 'venu4', + '4644': 'venu4s', '4647': 'approachS44', '4655': 'edge_mtb', '4656': 'approachS50', '4666': 'fenix_e', + '4745': 'bounce2', '4759': 'instinct3_solar_50mm', '4775': 'tactix8_amoled', '4776': 'tactix8_solar', + '4879': 'd2_mach2', + '4678': 'instinct_crossover_amoled', + '4944': 'd2_air_x15', '10007': 'sdm4', # SDM4 footpod '10014': 'edge_remote', '20533': 'tacx_training_app_win', diff --git a/garmin_fit_sdk/stream.py b/garmin_fit_sdk/stream.py index 6a85c79..71afb55 100644 --- a/garmin_fit_sdk/stream.py +++ b/garmin_fit_sdk/stream.py @@ -11,8 +11,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.178.0Release -# Tag = production/release/21.178.0-0-g3bea629 +# Profile Version = 21.188.0Release +# Tag = production/release/21.188.0-0-g55050f8 ############################################################################################ diff --git a/garmin_fit_sdk/util.py b/garmin_fit_sdk/util.py index bf89fc7..5f79d63 100644 --- a/garmin_fit_sdk/util.py +++ b/garmin_fit_sdk/util.py @@ -7,8 +7,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.178.0Release -# Tag = production/release/21.178.0-0-g3bea629 +# Profile Version = 21.188.0Release +# Tag = production/release/21.188.0-0-g55050f8 ############################################################################################ diff --git a/pyproject.toml b/pyproject.toml index 2f9ed12..5ee4bc9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,37 @@ [build-system] -requires = [ - "setuptools >= 48", - "setuptools_scm[toml] >= 4, <6", - "setuptools_scm_git_archive", - "wheel >= 0.29.0", +requires = ["setuptools >= 64"] +build-backend = "setuptools.build_meta" + +[project] +version = "21.188.0" +name = "garmin-fit-sdk" +description = "Garmin FIT Python SDK" +authors = [{ name = "Garmin International, Inc." }] +readme = { file = "README.md", content-type = "text/markdown" } +license = { file = "LICENSE" } +keywords = ["garmin", "fit sdk", "fit"] +requires-python = ">=3.6" +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Natural Language :: English", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3 :: Only", + "Operating System :: OS Independent", ] -build-backend = 'setuptools.build_meta' \ No newline at end of file +urls = { "Homepage" = "https://github.com/garmin/fit-python-sdk" } +dependencies = [] + +[project.optional-dependencies] +dev = ["pytest~=7.0.1", "pytest-cov~=3.0.0", "pytest-mock~=3.6.1"] + +[tool.setuptools] +include-package-data = true + +[tool.setuptools.packages.find] +exclude = [".gitignore", ".vscode*", ".pytest_cache*", ".test.py"] diff --git a/tests/__init__.py b/tests/__init__.py index 4749f05..3535a2f 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -8,4 +8,4 @@ # __init__.py for the fit sdk tests module -__version__ = '21.178.0' \ No newline at end of file +__version__ = '21.188.0' \ No newline at end of file diff --git a/tests/data.py b/tests/data.py index f6bac72..66dd916 100644 --- a/tests/data.py +++ b/tests/data.py @@ -174,6 +174,16 @@ class Data: 0x5C, 0xF2, 0x6D, 0x00, 0x36, 0xEE, 0x80, 0x78, 0x3B ]) + fit_file_short_multibyte_dev_data = bytearray([ + 0x0E, 0x20, 0xB9, 0x52, 0x3F, 0x00, 0x00, 0x00, 0x2E, 0x46, 0x49, 0x54, + 0xED, 0x8A, 0x40, 0x00, 0x01, 0x00, 0xCF, 0x01, 0x03, 0x01, 0x02, 0x00, + 0x00, 0x40, 0x00, 0x01, 0x00, 0xCE, 0x05, 0x00, 0x01, 0x02, 0x01, 0x01, + 0x02, 0x02, 0x01, 0x02, 0x08, 0x07, 0x07, 0x0E, 0x02, 0x84, 0x00, 0x00, + 0x00, 0x84, 0x70, 0x6F, 0x69, 0x6E, 0x74, 0x73, 0x00, 0x00, 0x12, 0x60, + 0x00, 0x01, 0x00, 0x12, 0x01, 0xFE, 0x02, 0x84, 0x01, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x04, 0xD2, 0x97, 0x59 + ]) + fit_file_monitoring = bytearray([ 0x0E, 0x10, 0x28, 0x23, 0x37, 0x00, 0x00, 0x00, 0x2E, 0x46, 0x49, 0x54, 0x2C, 0xC6, 0x41, 0x00, 0x01, 0x00, 0x37, 0x03, 0xFD, 0x04, 0x86, 0x18, diff --git a/tests/test_decoder.py b/tests/test_decoder.py index 981929f..38c918c 100644 --- a/tests/test_decoder.py +++ b/tests/test_decoder.py @@ -12,7 +12,7 @@ import pytest from garmin_fit_sdk import Decoder, Stream, CrcCalculator -from garmin_fit_sdk.decoder import DecodeMode +from garmin_fit_sdk.decoder import DecodeMode from tests.data import Data @@ -123,7 +123,7 @@ def test_no_header_with_data_only(self): assert len(errors) == 0 assert len(messages['file_id_mesgs']) == 1 - + def test_no_header_without_data_only(self): '''Tests that file with no header fails when decode mode is data only''' stream = Stream.from_byte_array(Data.fit_file_short_data_only) @@ -131,7 +131,7 @@ def test_no_header_without_data_only(self): messages, errors = decoder.read() assert len(errors) == 1 - + def test_invalid_crc_with_data_only(self): '''Tests that file with invalid CRC should not fail when decode mode is data only''' stream = Stream.from_byte_array(Data.fit_file_short_new_invalid_crc[14:]) @@ -373,6 +373,15 @@ def test_read_dev_data_no_field_description(self): assert len(errors) == 0 and len(messages['activity_mesgs']) == 1 + def test_read_multibyte_dev_data(self): + '''Tests reading developer data with a multi-byte base type.''' + stream = Stream.from_byte_array(Data.fit_file_short_multibyte_dev_data) + decoder = Decoder(stream) + messages, errors = decoder.read() + + assert len(errors) == 0 and len(messages['session_mesgs']) == 1 + assert messages['session_mesgs'][0]['developer_fields'][0] == 1234 + @pytest.mark.parametrize( "option_status", [ @@ -657,7 +666,7 @@ def test_expanded_components_which_accumulate_and_have_initial_value_scale_and_a assert messages['record_mesgs'][0]['distance'] == 2 assert messages['record_mesgs'][1]['distance'] == 264 assert messages['record_mesgs'][2]['distance'] == 276 - + class TestDecoderExceptions: '''Set of tests which verifies behavior of the decoder when various exceptions are raised''' @pytest.mark.parametrize( @@ -671,11 +680,11 @@ def test_keyboard_interrupt_and_system_exit_exceptions_are_rethrown(self, mocker '''Tests to ensure that the decoder rethrows KeyboardInterrupt and SystemExit exceptions''' stream = Stream.from_byte_array(Data.fit_file_short) decoder = Decoder(stream) - + mocked_is_fit = mocker.patch('garmin_fit_sdk.Decoder.is_fit') mocked_is_fit.side_effect = exception - with pytest.raises(exception): + with pytest.raises(exception): decoder.read() @pytest.mark.parametrize( @@ -692,7 +701,7 @@ def test_other_exceptions_are_not_rethrown(self, mocker, exception): '''Tests to ensure that the decoder does not rethrow other exceptions''' stream = Stream.from_byte_array(Data.fit_file_short) decoder = Decoder(stream) - + mocked_is_fit = mocker.patch('garmin_fit_sdk.Decoder.is_fit') mocked_is_fit.side_effect = exception