From 20cd88f8a094572f6863412faa2ccb3189bd331b Mon Sep 17 00:00:00 2001 From: Jenkins Date: Tue, 6 Oct 2026 12:24:44 -0500 Subject: [PATCH] Garmin FIT SDK 21.218.0 --- garmin_fit_sdk/__init__.py | 6 +-- garmin_fit_sdk/__init__.pyi | 4 +- garmin_fit_sdk/accumulator.py | 4 +- garmin_fit_sdk/accumulator.pyi | 4 +- garmin_fit_sdk/bitstream.py | 4 +- garmin_fit_sdk/bitstream.pyi | 4 +- garmin_fit_sdk/crc_calculator.py | 4 +- garmin_fit_sdk/crc_calculator.pyi | 4 +- garmin_fit_sdk/decoder.py | 4 +- garmin_fit_sdk/decoder.pyi | 4 +- garmin_fit_sdk/encoder.py | 66 ++++++++++-------------- garmin_fit_sdk/encoder.pyi | 4 +- garmin_fit_sdk/fit.py | 4 +- garmin_fit_sdk/fit.pyi | 4 +- garmin_fit_sdk/hr_mesg_utils.py | 4 +- garmin_fit_sdk/hr_mesg_utils.pyi | 4 +- garmin_fit_sdk/mesg_definition.py | 4 +- garmin_fit_sdk/mesg_definition.pyi | 4 +- garmin_fit_sdk/mesgs.pyi | 4 +- garmin_fit_sdk/output_stream.py | 4 +- garmin_fit_sdk/profile.py | 9 ++-- garmin_fit_sdk/profile.pyi | 4 +- garmin_fit_sdk/stream.py | 4 +- garmin_fit_sdk/types.pyi | 6 +-- garmin_fit_sdk/util.py | 4 +- garmin_fit_sdk/util.pyi | 4 +- pyproject.toml | 12 +++-- tests/__init__.py | 2 +- tests/test_encoder.py | 29 ++++++++++- tests/test_mesg_definition.py | 81 ++++++++++++++++++++++++------ 30 files changed, 187 insertions(+), 112 deletions(-) diff --git a/garmin_fit_sdk/__init__.py b/garmin_fit_sdk/__init__.py index 815710c..7e8ddc9 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.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ @@ -23,4 +23,4 @@ from garmin_fit_sdk.stream import Stream from garmin_fit_sdk.util import FIT_EPOCH_S, convert_datetime_to_timestamp, convert_timestamp_to_datetime, BASE_TYPE_TO_FIELD_TYPE, FIELD_TYPE_TO_BASE_TYPE -__version__ = '21.217.0' +__version__ = '21.218.0' diff --git a/garmin_fit_sdk/__init__.pyi b/garmin_fit_sdk/__init__.pyi index 22c3f07..6434683 100644 --- a/garmin_fit_sdk/__init__.pyi +++ b/garmin_fit_sdk/__init__.pyi @@ -5,8 +5,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ diff --git a/garmin_fit_sdk/accumulator.py b/garmin_fit_sdk/accumulator.py index c0f5865..c8f888e 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.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ diff --git a/garmin_fit_sdk/accumulator.pyi b/garmin_fit_sdk/accumulator.pyi index 9910c65..0d292b8 100644 --- a/garmin_fit_sdk/accumulator.pyi +++ b/garmin_fit_sdk/accumulator.pyi @@ -5,8 +5,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ diff --git a/garmin_fit_sdk/bitstream.py b/garmin_fit_sdk/bitstream.py index 747f7f9..025b8f6 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.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ diff --git a/garmin_fit_sdk/bitstream.pyi b/garmin_fit_sdk/bitstream.pyi index ba9328e..3f49b9d 100644 --- a/garmin_fit_sdk/bitstream.pyi +++ b/garmin_fit_sdk/bitstream.pyi @@ -5,8 +5,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ diff --git a/garmin_fit_sdk/crc_calculator.py b/garmin_fit_sdk/crc_calculator.py index e240bc0..27dc274 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.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ diff --git a/garmin_fit_sdk/crc_calculator.pyi b/garmin_fit_sdk/crc_calculator.pyi index c353d6f..5b99f2c 100644 --- a/garmin_fit_sdk/crc_calculator.pyi +++ b/garmin_fit_sdk/crc_calculator.pyi @@ -5,8 +5,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ diff --git a/garmin_fit_sdk/decoder.py b/garmin_fit_sdk/decoder.py index c33a537..c55f0ed 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.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ diff --git a/garmin_fit_sdk/decoder.pyi b/garmin_fit_sdk/decoder.pyi index a221fd6..63283a9 100644 --- a/garmin_fit_sdk/decoder.pyi +++ b/garmin_fit_sdk/decoder.pyi @@ -5,8 +5,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ diff --git a/garmin_fit_sdk/encoder.py b/garmin_fit_sdk/encoder.py index 330887f..25a8a83 100644 --- a/garmin_fit_sdk/encoder.py +++ b/garmin_fit_sdk/encoder.py @@ -7,8 +7,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ @@ -90,7 +90,14 @@ def on_mesg(self, mesg_num, mesg): """ try: mesg_definition = self._create_mesg_definition(mesg_num, mesg) - self._write_mesg_definition_if_not_active(mesg_definition) + local_mesg_definition = self._find_local_mesg_definition(mesg_definition) + + if local_mesg_definition is not None: + mesg_definition = local_mesg_definition + else: + mesg_definition.local_mesg_num = self._next_local_mesg_num & FIT.LOCAL_MESG_NUM_MASK + self._next_local_mesg_num += 1 + self._write_mesg_definition(mesg_definition) # Write message header self._output_stream.write_uint8(mesg_definition.local_mesg_num) @@ -190,22 +197,18 @@ def _transform_value(self, value, field_definition): invalid = FIT.BASE_TYPE_DEFINITIONS[base_type]['invalid'] try: - # None/null => invalid - if FIT.is_none(value): - return invalid - # Not a number, string, date, or bool => invalid if FIT.is_not_number_string_date_or_boolean(value): - raise ValueError() + raise ValueError("Value is not a number, string, date, or boolean") # Numeric field if field_type in FIT.NUMERIC_FIELD_TYPES: if FIT.is_string(value): value = float(value) if '.' in value else int(value) elif FIT.is_boolean(value): - raise ValueError() + raise ValueError("Boolean value not allowed for numeric field") elif not FIT.is_numeric(value): - raise ValueError() + raise ValueError("Value is not numeric for numeric field") has_scale_or_offset = ( field_definition['scale'] != FIT.FIELD_DEFAULT_SCALE @@ -225,13 +228,13 @@ def _transform_value(self, value, field_definition): if FIT.is_date(value): return convert_datetime_to_timestamp(value) if not FIT.is_numeric(value): - raise ValueError() + raise ValueError("Value is not numeric or date for date_time field") return value # String field if field_type == 'string': if not FIT.is_string(value): - raise ValueError() + raise ValueError("Value is not a string for string field") return value # Profile type field - try numeric passthrough first @@ -244,10 +247,10 @@ def _transform_value(self, value, field_definition): if type_value == value: return type_key - raise ValueError() + raise ValueError(f'Value "{value}" could not be mapped to profile type "{field_type}"') - except Exception: - raise ValueError(f'Could not convert "{value}" to "{field_type}"') + except Exception as exception: + raise ValueError(f'Could not convert "{value}" to "{field_type}": {exception}') @staticmethod def _unapply_scale_and_offset(value, scale, offset): @@ -263,36 +266,21 @@ def _create_mesg_definition(self, mesg_num, mesg): Returns: _MesgDefinition: The created message definition. """ - mesg_definition = _MesgDefinition(mesg_num, mesg, field_descriptions=self._field_descriptions) - mesg_definition.local_mesg_num = self._lookup_local_mesg_num(mesg_definition) - return mesg_definition + return _MesgDefinition(mesg_num, mesg, field_descriptions=self._field_descriptions) - def _lookup_local_mesg_num(self, mesg_definition): - """Searches the local mesg definitions for a matching mesg_definition. + def _find_local_mesg_definition(self, mesg_definition): + """Returns the active local mesg definition equivalent to mesg_definition, if any. Args: - mesg_definition: The mesg definition to match. + mesg_definition: The candidate message definition to match. Returns: - int: The local_mesg_num to be used with mesg_definition. + _MesgDefinition | None: The matching active local mesg definition, if present. """ - for i, local_def in enumerate(self._local_mesg_definitions): + for local_def in self._local_mesg_definitions: if local_def is not None and local_def.equals(mesg_definition): - return i - result = self._next_local_mesg_num - self._next_local_mesg_num += 1 - return result & FIT.LOCAL_MESG_NUM_MASK - - def _write_mesg_definition_if_not_active(self, mesg_definition): - """Writes the mesg_definition to the output stream if it is not one of the currently active 16. - - Args: - mesg_definition: The mesg definition to match. - """ - local_num = mesg_definition.local_mesg_num - existing = self._local_mesg_definitions[local_num] - if existing is None or not existing.equals(mesg_definition): - self._write_mesg_definition(mesg_definition) + return local_def + return None def _write_mesg_definition(self, mesg_definition): """Writes the mesg_definition to the output stream. @@ -301,4 +289,4 @@ def _write_mesg_definition(self, mesg_definition): mesg_definition: The mesg definition to write. """ mesg_definition.write(self._output_stream) - self._local_mesg_definitions[mesg_definition.local_mesg_num] = mesg_definition + self._local_mesg_definitions[mesg_definition.local_mesg_num] = mesg_definition \ No newline at end of file diff --git a/garmin_fit_sdk/encoder.pyi b/garmin_fit_sdk/encoder.pyi index 2f595eb..c4fbdb7 100644 --- a/garmin_fit_sdk/encoder.pyi +++ b/garmin_fit_sdk/encoder.pyi @@ -5,8 +5,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ diff --git a/garmin_fit_sdk/fit.py b/garmin_fit_sdk/fit.py index bb2f6f3..5edd2b2 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.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ diff --git a/garmin_fit_sdk/fit.pyi b/garmin_fit_sdk/fit.pyi index 8b30104..e8e391b 100644 --- a/garmin_fit_sdk/fit.pyi +++ b/garmin_fit_sdk/fit.pyi @@ -5,8 +5,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ diff --git a/garmin_fit_sdk/hr_mesg_utils.py b/garmin_fit_sdk/hr_mesg_utils.py index 1cda8a6..2954ca4 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.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ diff --git a/garmin_fit_sdk/hr_mesg_utils.pyi b/garmin_fit_sdk/hr_mesg_utils.pyi index 1da5bf8..b4f595e 100644 --- a/garmin_fit_sdk/hr_mesg_utils.pyi +++ b/garmin_fit_sdk/hr_mesg_utils.pyi @@ -5,8 +5,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ diff --git a/garmin_fit_sdk/mesg_definition.py b/garmin_fit_sdk/mesg_definition.py index ccc2e0f..397e5f2 100644 --- a/garmin_fit_sdk/mesg_definition.py +++ b/garmin_fit_sdk/mesg_definition.py @@ -7,8 +7,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ diff --git a/garmin_fit_sdk/mesg_definition.pyi b/garmin_fit_sdk/mesg_definition.pyi index cefd098..177452c 100644 --- a/garmin_fit_sdk/mesg_definition.pyi +++ b/garmin_fit_sdk/mesg_definition.pyi @@ -5,8 +5,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ diff --git a/garmin_fit_sdk/mesgs.pyi b/garmin_fit_sdk/mesgs.pyi index ed994ef..2af6e92 100644 --- a/garmin_fit_sdk/mesgs.pyi +++ b/garmin_fit_sdk/mesgs.pyi @@ -5,8 +5,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ diff --git a/garmin_fit_sdk/output_stream.py b/garmin_fit_sdk/output_stream.py index e119db3..45db086 100644 --- a/garmin_fit_sdk/output_stream.py +++ b/garmin_fit_sdk/output_stream.py @@ -7,8 +7,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ diff --git a/garmin_fit_sdk/profile.py b/garmin_fit_sdk/profile.py index bcab34b..22f2f45 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.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ Profile = { 'version': { 'major': 21, - 'minor': 217, + 'minor': 218, 'patch': 0, 'type': "Release" }, @@ -28464,6 +28464,8 @@ 4814: 'fr170_music', 4815: 'fr170', 4825: 'approach_j1', + 4874: 'approach_s72_43mm', + 4875: 'approach_s72_47mm', 4879: 'd2_mach2', 4916: 'fr70_2026', 4952: 'fenix9_pro_43mm', @@ -28477,6 +28479,7 @@ 4944: 'd2_air_x15', 5019: 'cirqa_smart_band', 5056: 'd2_mach2_pro', + 5169: 'enduro4', 10007: 'sdm4', # SDM4 footpod 10014: 'edge_remote', 20533: 'tacx_training_app_win', diff --git a/garmin_fit_sdk/profile.pyi b/garmin_fit_sdk/profile.pyi index 69f6071..3f58dfb 100644 --- a/garmin_fit_sdk/profile.pyi +++ b/garmin_fit_sdk/profile.pyi @@ -5,8 +5,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ diff --git a/garmin_fit_sdk/stream.py b/garmin_fit_sdk/stream.py index e7380e1..27f2bbf 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.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ diff --git a/garmin_fit_sdk/types.pyi b/garmin_fit_sdk/types.pyi index ce22e9e..0f0e97e 100644 --- a/garmin_fit_sdk/types.pyi +++ b/garmin_fit_sdk/types.pyi @@ -5,8 +5,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ @@ -188,7 +188,7 @@ CoursePoint = Union[int, Literal["generic"], Literal["summit"], Literal["valley" Manufacturer = Union[int, Literal["garmin"], Literal["garminFr405Antfs"], Literal["zephyr"], Literal["dayton"], Literal["idt"], Literal["srm"], Literal["quarq"], Literal["ibike"], Literal["saris"], Literal["sparkHk"], Literal["tanita"], Literal["echowell"], Literal["dynastreamOem"], Literal["nautilus"], Literal["dynastream"], Literal["timex"], Literal["metrigear"], Literal["xelic"], Literal["beurer"], Literal["cardiosport"], Literal["aAndD"], Literal["hmm"], Literal["suunto"], Literal["thitaElektronik"], Literal["gpulse"], Literal["cleanMobile"], Literal["pedalBrain"], Literal["peaksware"], Literal["saxonar"], Literal["lemondFitness"], Literal["dexcom"], Literal["wahooFitness"], Literal["octaneFitness"], Literal["archinoetics"], Literal["theHurtBox"], Literal["citizenSystems"], Literal["magellan"], Literal["osynce"], Literal["holux"], Literal["concept2"], Literal["shimano"], Literal["oneGiantLeap"], Literal["aceSensor"], Literal["brimBrothers"], Literal["xplova"], Literal["perceptionDigital"], Literal["bf1systems"], Literal["pioneer"], Literal["spantec"], Literal["metalogics"], Literal["4iiiis"], Literal["seikoEpson"], Literal["seikoEpsonOem"], Literal["iforPowell"], Literal["maxwellGuider"], Literal["starTrac"], Literal["breakaway"], Literal["alatechTechnologyLtd"], Literal["mioTechnologyEurope"], Literal["rotor"], Literal["geonaute"], Literal["idBike"], Literal["specialized"], Literal["wtek"], Literal["physicalEnterprises"], Literal["northPoleEngineering"], Literal["bkool"], Literal["cateye"], Literal["stagesCycling"], Literal["sigmasport"], Literal["tomtom"], Literal["peripedal"], Literal["wattbike"], Literal["moxy"], Literal["ciclosport"], Literal["powerbahn"], Literal["acornProjectsAps"], Literal["lifebeam"], Literal["bontrager"], Literal["wellgo"], Literal["scosche"], Literal["magura"], Literal["woodway"], Literal["elite"], Literal["nielsenKellerman"], Literal["dkCity"], Literal["tacx"], Literal["directionTechnology"], Literal["magtonic"], Literal["1partcarbon"], Literal["insideRideTechnologies"], Literal["soundOfMotion"], Literal["stryd"], Literal["icg"], Literal["miPulse"], Literal["bsxAthletics"], Literal["look"], Literal["campagnoloSrl"], Literal["bodyBikeSmart"], Literal["praxisworks"], Literal["limitsTechnology"], Literal["topactionTechnology"], Literal["cosinuss"], Literal["fitcare"], Literal["magene"], Literal["giantManufacturingCo"], Literal["tigrasport"], Literal["salutron"], Literal["technogym"], Literal["brytonSensors"], Literal["latitudeLimited"], Literal["soaringTechnology"], Literal["igpsport"], Literal["thinkrider"], Literal["gopherSport"], Literal["waterrower"], Literal["orangetheory"], Literal["inpeak"], Literal["kinetic"], Literal["johnsonHealthTech"], Literal["polarElectro"], Literal["seesense"], Literal["nciTechnology"], Literal["iqsquare"], Literal["leomo"], Literal["ifitCom"], Literal["corosByte"], Literal["versaDesign"], Literal["chileaf"], Literal["cycplus"], Literal["gravaaByte"], Literal["sigeyi"], Literal["coospo"], Literal["geoid"], Literal["bosch"], Literal["kyto"], Literal["kineticSports"], Literal["decathlonByte"], Literal["tqSystems"], Literal["tagHeuer"], Literal["keiserFitness"], Literal["zwiftByte"], Literal["porscheEp"], Literal["blackbird"], Literal["meilanByte"], Literal["ezon"], Literal["laisi"], Literal["myzone"], Literal["abawo"], Literal["bafang"], Literal["luhongTechnology"], Literal["development"], Literal["healthandlife"], Literal["lezyne"], Literal["scribeLabs"], Literal["zwift"], Literal["watteam"], Literal["recon"], Literal["faveroElectronics"], Literal["dynovelo"], Literal["strava"], Literal["precor"], Literal["bryton"], Literal["sram"], Literal["navman"], Literal["cobi"], Literal["spivi"], Literal["mioMagellan"], Literal["evesports"], Literal["sensitivusGauge"], Literal["podoon"], Literal["lifeTimeFitness"], Literal["falcoEMotors"], Literal["minoura"], Literal["cycliq"], Literal["luxottica"], Literal["trainerRoad"], Literal["theSufferfest"], Literal["fullspeedahead"], Literal["virtualtraining"], Literal["feedbacksports"], Literal["omata"], Literal["vdo"], Literal["magneticdays"], Literal["hammerhead"], Literal["kineticByKurt"], Literal["shapelog"], Literal["dabuziduo"], Literal["jetblack"], Literal["coros"], Literal["virtugo"], Literal["velosense"], Literal["cycligentinc"], Literal["trailforks"], Literal["mahleEbikemotion"], Literal["nurvv"], Literal["microprogram"], Literal["zone5cloud"], Literal["greenteg"], Literal["yamahaMotors"], Literal["whoop"], Literal["gravaa"], Literal["onelap"], Literal["monarkExercise"], Literal["form"], Literal["decathlon"], Literal["syncros"], Literal["heatup"], Literal["cannondale"], Literal["trueFitness"], Literal["rGTCycling"], Literal["vasa"], Literal["raceRepublic"], Literal["fazua"], Literal["orekaTraining"], Literal["lsec"], Literal["lululemonStudio"], Literal["shanyue"], Literal["spinningMda"], Literal["hilldating"], Literal["aeroSensor"], Literal["nike"], Literal["magicshine"], Literal["ictrainer"], Literal["absoluteCycling"], Literal["eoSwimbetter"], Literal["mywhoosh"], Literal["ravemen"], Literal["tektroRacingProducts"], Literal["daradInnovationCorporation"], Literal["cycloptim"], Literal["runna"], Literal["zepp"], Literal["peloton"], Literal["carv"], Literal["tissot"], Literal["realVelo"], Literal["wetech"], Literal["jespr"], Literal["huawei"], Literal["gotoes"], Literal["cadenceApp"], Literal["unaWatch"], Literal["tymewear"], Literal["exposureLights"], Literal["fortoCycling"], Literal["actigraphcorp"]] -GarminProduct = Union[int, Literal["hrm1"], Literal["axh01"], Literal["axb01"], Literal["axb02"], Literal["hrm2ss"], Literal["dsiAlf02"], Literal["hrm3ss"], Literal["hrmRunSingleByteProductId"], Literal["bsm"], Literal["bcm"], Literal["axs01"], Literal["hrmTriSingleByteProductId"], Literal["hrm4RunSingleByteProductId"], Literal["fr225SingleByteProductId"], Literal["gen3BsmSingleByteProductId"], Literal["gen3BcmSingleByteProductId"], Literal["hrmFitSingleByteProductId"], Literal["oHR"], Literal["fr301China"], Literal["fr301Japan"], Literal["fr301Korea"], Literal["fr301Taiwan"], Literal["fr405"], Literal["fr50"], Literal["fr405Japan"], Literal["fr60"], Literal["dsiAlf01"], Literal["fr310xt"], Literal["edge500"], Literal["fr110"], Literal["edge800"], Literal["edge500Taiwan"], Literal["edge500Japan"], Literal["chirp"], Literal["fr110Japan"], Literal["edge200"], Literal["fr910xt"], Literal["edge800Taiwan"], Literal["edge800Japan"], Literal["alf04"], Literal["fr610"], Literal["fr210Japan"], Literal["vectorSs"], Literal["vectorCp"], Literal["edge800China"], Literal["edge500China"], Literal["approachG10"], Literal["fr610Japan"], Literal["edge500Korea"], Literal["fr70"], Literal["fr310xt4t"], Literal["amx"], Literal["fr10"], Literal["edge800Korea"], Literal["swim"], Literal["fr910xtChina"], Literal["fenix"], Literal["edge200Taiwan"], Literal["edge510"], Literal["edge810"], Literal["tempe"], Literal["fr910xtJapan"], Literal["fr620"], Literal["fr220"], Literal["fr910xtKorea"], Literal["fr10Japan"], Literal["edge810Japan"], Literal["virbElite"], Literal["edgeTouring"], Literal["edge510Japan"], Literal["hrmTri"], Literal["hrmRun"], Literal["fr920xt"], Literal["edge510Asia"], Literal["edge810China"], Literal["edge810Taiwan"], Literal["edge1000"], Literal["vivoFit"], Literal["virbRemote"], Literal["vivoKi"], Literal["fr15"], Literal["vivoActive"], Literal["edge510Korea"], Literal["fr620Japan"], Literal["fr620China"], Literal["fr220Japan"], Literal["fr220China"], Literal["approachS6"], Literal["vivoSmart"], Literal["fenix2"], Literal["epix"], Literal["fenix3"], Literal["edge1000Taiwan"], Literal["edge1000Japan"], Literal["fr15Japan"], Literal["edge520"], Literal["edge1000China"], Literal["fr620Russia"], Literal["fr220Russia"], Literal["vectorS"], Literal["edge1000Korea"], Literal["fr920xtTaiwan"], Literal["fr920xtChina"], Literal["fr920xtJapan"], Literal["virbx"], Literal["vivoSmartApac"], Literal["etrexTouch"], Literal["edge25"], Literal["fr25"], Literal["vivoFit2"], Literal["fr225"], Literal["fr630"], Literal["fr230"], Literal["fr735xt"], Literal["vivoActiveApac"], Literal["vector2"], Literal["vector2s"], Literal["virbxe"], Literal["fr620Taiwan"], Literal["fr220Taiwan"], Literal["truswing"], Literal["d2airvenu"], Literal["fenix3China"], Literal["fenix3Twn"], Literal["variaHeadlight"], Literal["variaTaillightOld"], Literal["edgeExplore1000"], Literal["fr225Asia"], Literal["variaRadarTaillight"], Literal["variaRadarDisplay"], Literal["edge20"], Literal["edge520Asia"], Literal["edge520Japan"], Literal["d2Bravo"], Literal["approachS20"], Literal["vivoSmart2"], Literal["edge1000Thai"], Literal["variaRemote"], Literal["edge25Asia"], Literal["edge25Jpn"], Literal["edge20Asia"], Literal["approachX40"], Literal["fenix3Japan"], Literal["vivoSmartEmea"], Literal["fr630Asia"], Literal["fr630Jpn"], Literal["fr230Jpn"], Literal["hrm4Run"], Literal["epixJapan"], Literal["vivoActiveHr"], Literal["vivoSmartGpsHr"], Literal["vivoSmartHr"], Literal["vivoSmartHrAsia"], Literal["vivoSmartGpsHrAsia"], Literal["vivoMove"], Literal["variaTaillight"], Literal["fr235Asia"], Literal["fr235Japan"], Literal["variaVision"], Literal["vivoFit3"], Literal["fenix3Korea"], Literal["fenix3Sea"], Literal["fenix3Hr"], Literal["virbUltra30"], Literal["indexSmartScale"], Literal["fr235"], Literal["fenix3Chronos"], Literal["oregon7xx"], Literal["rino7xx"], Literal["epixKorea"], Literal["fenix3HrChn"], Literal["fenix3HrTwn"], Literal["fenix3HrJpn"], Literal["fenix3HrSea"], Literal["fenix3HrKor"], Literal["nautix"], Literal["vivoActiveHrApac"], Literal["fr35"], Literal["oregon7xxWw"], Literal["edge820"], Literal["edgeExplore820"], Literal["fr735xtApac"], Literal["fr735xtJapan"], Literal["fenix5s"], Literal["d2BravoTitanium"], Literal["variaUt800"], Literal["runningDynamicsPod"], Literal["edge820China"], Literal["edge820Japan"], Literal["fenix5x"], Literal["vivoFitJr"], Literal["vivoSmart3"], Literal["vivoSport"], Literal["edge820Taiwan"], Literal["edge820Korea"], Literal["edge820Sea"], Literal["fr35Hebrew"], Literal["approachS60"], Literal["fr35Apac"], Literal["fr35Japan"], Literal["fenix3ChronosAsia"], Literal["virb360"], Literal["fr935"], Literal["fenix5"], Literal["vivoactive3"], Literal["fr235ChinaNfc"], Literal["foretrex601_701"], Literal["vivoMoveHr"], Literal["edge1030"], Literal["fr35Sea"], Literal["vector3"], Literal["fenix5Asia"], Literal["fenix5sAsia"], Literal["fenix5xAsia"], Literal["approachZ80"], Literal["fr35Korea"], Literal["d2charlie"], Literal["vivoSmart3Apac"], Literal["vivoSportApac"], Literal["fr935Asia"], Literal["descent"], Literal["vivoFit4"], Literal["fr645"], Literal["fr645m"], Literal["fr30"], Literal["fenix5sPlus"], Literal["edge130"], Literal["edge1030Asia"], Literal["vivosmart4"], Literal["vivoMoveHrAsia"], Literal["approachX10"], Literal["fr30Asia"], Literal["vivoactive3mW"], Literal["fr645Asia"], Literal["fr645mAsia"], Literal["edgeExplore"], Literal["gpsmap66"], Literal["approachS10"], Literal["vivoactive3mL"], Literal["fr245"], Literal["fr245Music"], Literal["approachG80"], Literal["edge130Asia"], Literal["edge1030Bontrager"], Literal["fenix5Plus"], Literal["fenix5xPlus"], Literal["edge520Plus"], Literal["fr945"], Literal["edge530"], Literal["edge830"], Literal["instinctEsports"], Literal["fenix5sPlusApac"], Literal["fenix5xPlusApac"], Literal["edge520PlusApac"], Literal["descentT1"], Literal["fr235lAsia"], Literal["fr245Asia"], Literal["vivoActive3mApac"], Literal["gen3Bsm"], Literal["gen3Bcm"], Literal["vivoSmart4Asia"], Literal["vivoactive4Small"], Literal["vivoactive4Large"], Literal["venu"], Literal["marqDriver"], Literal["marqAviator"], Literal["marqCaptain"], Literal["marqCommander"], Literal["marqExpedition"], Literal["marqAthlete"], Literal["descentMk2"], Literal["fr45"], Literal["gpsmap66i"], Literal["fenix6SSport"], Literal["fenix6S"], Literal["fenix6Sport"], Literal["fenix6"], Literal["fenix6x"], Literal["hrmDual"], Literal["hrmPro"], Literal["vivoMove3Premium"], Literal["approachS40"], Literal["fr245mAsia"], Literal["edge530Apac"], Literal["edge830Apac"], Literal["vivoMove3"], Literal["vivoActive4SmallAsia"], Literal["vivoActive4LargeAsia"], Literal["vivoActive4OledAsia"], Literal["swim2"], Literal["marqDriverAsia"], Literal["marqAviatorAsia"], Literal["vivoMove3Asia"], Literal["fr945Asia"], Literal["vivoActive3tChn"], Literal["marqCaptainAsia"], Literal["marqCommanderAsia"], Literal["marqExpeditionAsia"], Literal["marqAthleteAsia"], Literal["indexSmartScale2"], Literal["instinctSolar"], Literal["fr45Asia"], Literal["vivoactive3Daimler"], Literal["legacyRey"], Literal["legacyDarthVader"], Literal["legacyCaptainMarvel"], Literal["legacyFirstAvenger"], Literal["fenix6sSportAsia"], Literal["fenix6sAsia"], Literal["fenix6SportAsia"], Literal["fenix6Asia"], Literal["fenix6xAsia"], Literal["legacyCaptainMarvelAsia"], Literal["legacyFirstAvengerAsia"], Literal["legacyReyAsia"], Literal["legacyDarthVaderAsia"], Literal["descentMk2s"], Literal["edge130Plus"], Literal["edge1030Plus"], Literal["rally200"], Literal["fr745"], Literal["venusqMusic"], Literal["venusqMusicV2"], Literal["venusq"], Literal["lily"], Literal["marqAdventurer"], Literal["enduro"], Literal["swim2Apac"], Literal["marqAdventurerAsia"], Literal["fr945Lte"], Literal["descentMk2Asia"], Literal["venu2"], Literal["venu2s"], Literal["venuDaimlerAsia"], Literal["marqGolfer"], Literal["venuDaimler"], Literal["fr745Asia"], Literal["variaRct715"], Literal["lilyAsia"], Literal["edge1030PlusAsia"], Literal["edge130PlusAsia"], Literal["approachS12"], Literal["enduroAsia"], Literal["venusqAsia"], Literal["edge1040"], Literal["marqGolferAsia"], Literal["venu2Plus"], Literal["gnss"], Literal["fr55"], Literal["instinct2"], Literal["instinct2s"], Literal["fenix7s"], Literal["fenix7"], Literal["fenix7x"], Literal["fenix7sApac"], Literal["fenix7Apac"], Literal["fenix7xApac"], Literal["approachG12"], Literal["descentMk2sAsia"], Literal["approachS42"], Literal["epixGen2"], Literal["epixGen2Apac"], Literal["venu2sAsia"], Literal["venu2Asia"], Literal["fr945LteAsia"], Literal["vivoMoveSport"], Literal["vivomoveTrend"], Literal["approachS12Asia"], Literal["fr255Music"], Literal["fr255SmallMusic"], Literal["fr255"], Literal["fr255Small"], Literal["approachG12Asia"], Literal["approachS42Asia"], Literal["descentG1"], Literal["venu2PlusAsia"], Literal["fr955"], Literal["fr55Asia"], Literal["edge540"], Literal["edge840"], Literal["vivosmart5"], Literal["instinct2Asia"], Literal["marqGen2"], Literal["venusq2"], Literal["venusq2music"], Literal["marqGen2Aviator"], Literal["d2AirX10"], Literal["hrmProPlus"], Literal["descentG1Asia"], Literal["tactix7"], Literal["instinctCrossover"], Literal["edgeExplore2"], Literal["descentMk3"], Literal["descentMk3i"], Literal["approachS70"], Literal["fr265Large"], Literal["fr265Small"], Literal["venu3"], Literal["venu3s"], Literal["tacxNeoSmart"], Literal["tacxNeo2Smart"], Literal["tacxNeo2TSmart"], Literal["tacxNeoSmartBike"], Literal["tacxSatoriSmart"], Literal["tacxFlowSmart"], Literal["tacxVortexSmart"], Literal["tacxBushidoSmart"], Literal["tacxGeniusSmart"], Literal["tacxFluxFluxSSmart"], Literal["tacxFlux2Smart"], Literal["tacxMagnum"], Literal["edge1040Asia"], Literal["epixGen2Pro42"], Literal["epixGen2Pro47"], Literal["epixGen2Pro51"], Literal["fr965"], Literal["enduro2"], Literal["fenix7sProSolar"], Literal["fenix7ProSolar"], Literal["fenix7xProSolar"], Literal["lily2"], Literal["instinct2x"], Literal["vivoactive5"], Literal["fr165"], Literal["fr165Music"], Literal["edge1050"], Literal["descentT2"], Literal["hrmFit"], Literal["marqGen2Commander"], Literal["lilyAthlete"], Literal["rallyX10"], Literal["fenix8Solar"], Literal["fenix8SolarLarge"], Literal["fenix8Small"], Literal["fenix8"], Literal["d2Mach1Pro"], Literal["fr970"], Literal["fr570Large"], Literal["fr570Small"], Literal["enduro3"], Literal["instinctE40mm"], Literal["instinctE45mm"], Literal["instinct3Solar45mm"], Literal["instinct3Amoled45mm"], Literal["instinct3Amoled50mm"], Literal["descentG2"], Literal["fenix7ProSolarNoWifi"], Literal["venuX1"], Literal["hrm200"], Literal["vivoactive6"], Literal["fenix8Pro"], Literal["edge550"], Literal["edge850"], Literal["venu4"], Literal["venu4s"], Literal["approachS44"], Literal["edgeMtb"], Literal["approachS50"], Literal["fenixE"], Literal["bounce2"], Literal["instinct3Solar50mm"], Literal["tactix8Amoled"], Literal["tactix8Solar"], Literal["fr170Music"], Literal["fr170"], Literal["approachJ1"], Literal["d2Mach2"], Literal["fr702026"], Literal["fenix9Pro43mm"], Literal["fenix9Pro47mm"], Literal["fenix9Pro51mm"], Literal["fenix9ProSolar47mm"], Literal["fenix9ProSolar51mm"], Literal["fenix943mm"], Literal["fenix9"], Literal["instinctCrossoverAmoled"], Literal["d2AirX15"], Literal["cirqaSmartBand"], Literal["d2Mach2Pro"], Literal["sdm4"], Literal["edgeRemote"], Literal["tacxTrainingAppWin"], Literal["tacxTrainingAppMac"], Literal["tacxTrainingAppMacCatalyst"], Literal["trainingCenter"], Literal["tacxTrainingAppAndroid"], Literal["tacxTrainingAppIos"], Literal["tacxTrainingAppLegacy"], Literal["trainingPeaksVirtualIos"], Literal["trainingPeaksVirtualAndroid"], Literal["trainingPeaksVirtualWindows"], Literal["trainingPeaksVirtualMacOs"], Literal["trainingPeaksVirtualAppleTv"], Literal["connectiqSimulator"], Literal["androidAntplusPlugin"], Literal["connect"]] +GarminProduct = Union[int, Literal["hrm1"], Literal["axh01"], Literal["axb01"], Literal["axb02"], Literal["hrm2ss"], Literal["dsiAlf02"], Literal["hrm3ss"], Literal["hrmRunSingleByteProductId"], Literal["bsm"], Literal["bcm"], Literal["axs01"], Literal["hrmTriSingleByteProductId"], Literal["hrm4RunSingleByteProductId"], Literal["fr225SingleByteProductId"], Literal["gen3BsmSingleByteProductId"], Literal["gen3BcmSingleByteProductId"], Literal["hrmFitSingleByteProductId"], Literal["oHR"], Literal["fr301China"], Literal["fr301Japan"], Literal["fr301Korea"], Literal["fr301Taiwan"], Literal["fr405"], Literal["fr50"], Literal["fr405Japan"], Literal["fr60"], Literal["dsiAlf01"], Literal["fr310xt"], Literal["edge500"], Literal["fr110"], Literal["edge800"], Literal["edge500Taiwan"], Literal["edge500Japan"], Literal["chirp"], Literal["fr110Japan"], Literal["edge200"], Literal["fr910xt"], Literal["edge800Taiwan"], Literal["edge800Japan"], Literal["alf04"], Literal["fr610"], Literal["fr210Japan"], Literal["vectorSs"], Literal["vectorCp"], Literal["edge800China"], Literal["edge500China"], Literal["approachG10"], Literal["fr610Japan"], Literal["edge500Korea"], Literal["fr70"], Literal["fr310xt4t"], Literal["amx"], Literal["fr10"], Literal["edge800Korea"], Literal["swim"], Literal["fr910xtChina"], Literal["fenix"], Literal["edge200Taiwan"], Literal["edge510"], Literal["edge810"], Literal["tempe"], Literal["fr910xtJapan"], Literal["fr620"], Literal["fr220"], Literal["fr910xtKorea"], Literal["fr10Japan"], Literal["edge810Japan"], Literal["virbElite"], Literal["edgeTouring"], Literal["edge510Japan"], Literal["hrmTri"], Literal["hrmRun"], Literal["fr920xt"], Literal["edge510Asia"], Literal["edge810China"], Literal["edge810Taiwan"], Literal["edge1000"], Literal["vivoFit"], Literal["virbRemote"], Literal["vivoKi"], Literal["fr15"], Literal["vivoActive"], Literal["edge510Korea"], Literal["fr620Japan"], Literal["fr620China"], Literal["fr220Japan"], Literal["fr220China"], Literal["approachS6"], Literal["vivoSmart"], Literal["fenix2"], Literal["epix"], Literal["fenix3"], Literal["edge1000Taiwan"], Literal["edge1000Japan"], Literal["fr15Japan"], Literal["edge520"], Literal["edge1000China"], Literal["fr620Russia"], Literal["fr220Russia"], Literal["vectorS"], Literal["edge1000Korea"], Literal["fr920xtTaiwan"], Literal["fr920xtChina"], Literal["fr920xtJapan"], Literal["virbx"], Literal["vivoSmartApac"], Literal["etrexTouch"], Literal["edge25"], Literal["fr25"], Literal["vivoFit2"], Literal["fr225"], Literal["fr630"], Literal["fr230"], Literal["fr735xt"], Literal["vivoActiveApac"], Literal["vector2"], Literal["vector2s"], Literal["virbxe"], Literal["fr620Taiwan"], Literal["fr220Taiwan"], Literal["truswing"], Literal["d2airvenu"], Literal["fenix3China"], Literal["fenix3Twn"], Literal["variaHeadlight"], Literal["variaTaillightOld"], Literal["edgeExplore1000"], Literal["fr225Asia"], Literal["variaRadarTaillight"], Literal["variaRadarDisplay"], Literal["edge20"], Literal["edge520Asia"], Literal["edge520Japan"], Literal["d2Bravo"], Literal["approachS20"], Literal["vivoSmart2"], Literal["edge1000Thai"], Literal["variaRemote"], Literal["edge25Asia"], Literal["edge25Jpn"], Literal["edge20Asia"], Literal["approachX40"], Literal["fenix3Japan"], Literal["vivoSmartEmea"], Literal["fr630Asia"], Literal["fr630Jpn"], Literal["fr230Jpn"], Literal["hrm4Run"], Literal["epixJapan"], Literal["vivoActiveHr"], Literal["vivoSmartGpsHr"], Literal["vivoSmartHr"], Literal["vivoSmartHrAsia"], Literal["vivoSmartGpsHrAsia"], Literal["vivoMove"], Literal["variaTaillight"], Literal["fr235Asia"], Literal["fr235Japan"], Literal["variaVision"], Literal["vivoFit3"], Literal["fenix3Korea"], Literal["fenix3Sea"], Literal["fenix3Hr"], Literal["virbUltra30"], Literal["indexSmartScale"], Literal["fr235"], Literal["fenix3Chronos"], Literal["oregon7xx"], Literal["rino7xx"], Literal["epixKorea"], Literal["fenix3HrChn"], Literal["fenix3HrTwn"], Literal["fenix3HrJpn"], Literal["fenix3HrSea"], Literal["fenix3HrKor"], Literal["nautix"], Literal["vivoActiveHrApac"], Literal["fr35"], Literal["oregon7xxWw"], Literal["edge820"], Literal["edgeExplore820"], Literal["fr735xtApac"], Literal["fr735xtJapan"], Literal["fenix5s"], Literal["d2BravoTitanium"], Literal["variaUt800"], Literal["runningDynamicsPod"], Literal["edge820China"], Literal["edge820Japan"], Literal["fenix5x"], Literal["vivoFitJr"], Literal["vivoSmart3"], Literal["vivoSport"], Literal["edge820Taiwan"], Literal["edge820Korea"], Literal["edge820Sea"], Literal["fr35Hebrew"], Literal["approachS60"], Literal["fr35Apac"], Literal["fr35Japan"], Literal["fenix3ChronosAsia"], Literal["virb360"], Literal["fr935"], Literal["fenix5"], Literal["vivoactive3"], Literal["fr235ChinaNfc"], Literal["foretrex601_701"], Literal["vivoMoveHr"], Literal["edge1030"], Literal["fr35Sea"], Literal["vector3"], Literal["fenix5Asia"], Literal["fenix5sAsia"], Literal["fenix5xAsia"], Literal["approachZ80"], Literal["fr35Korea"], Literal["d2charlie"], Literal["vivoSmart3Apac"], Literal["vivoSportApac"], Literal["fr935Asia"], Literal["descent"], Literal["vivoFit4"], Literal["fr645"], Literal["fr645m"], Literal["fr30"], Literal["fenix5sPlus"], Literal["edge130"], Literal["edge1030Asia"], Literal["vivosmart4"], Literal["vivoMoveHrAsia"], Literal["approachX10"], Literal["fr30Asia"], Literal["vivoactive3mW"], Literal["fr645Asia"], Literal["fr645mAsia"], Literal["edgeExplore"], Literal["gpsmap66"], Literal["approachS10"], Literal["vivoactive3mL"], Literal["fr245"], Literal["fr245Music"], Literal["approachG80"], Literal["edge130Asia"], Literal["edge1030Bontrager"], Literal["fenix5Plus"], Literal["fenix5xPlus"], Literal["edge520Plus"], Literal["fr945"], Literal["edge530"], Literal["edge830"], Literal["instinctEsports"], Literal["fenix5sPlusApac"], Literal["fenix5xPlusApac"], Literal["edge520PlusApac"], Literal["descentT1"], Literal["fr235lAsia"], Literal["fr245Asia"], Literal["vivoActive3mApac"], Literal["gen3Bsm"], Literal["gen3Bcm"], Literal["vivoSmart4Asia"], Literal["vivoactive4Small"], Literal["vivoactive4Large"], Literal["venu"], Literal["marqDriver"], Literal["marqAviator"], Literal["marqCaptain"], Literal["marqCommander"], Literal["marqExpedition"], Literal["marqAthlete"], Literal["descentMk2"], Literal["fr45"], Literal["gpsmap66i"], Literal["fenix6SSport"], Literal["fenix6S"], Literal["fenix6Sport"], Literal["fenix6"], Literal["fenix6x"], Literal["hrmDual"], Literal["hrmPro"], Literal["vivoMove3Premium"], Literal["approachS40"], Literal["fr245mAsia"], Literal["edge530Apac"], Literal["edge830Apac"], Literal["vivoMove3"], Literal["vivoActive4SmallAsia"], Literal["vivoActive4LargeAsia"], Literal["vivoActive4OledAsia"], Literal["swim2"], Literal["marqDriverAsia"], Literal["marqAviatorAsia"], Literal["vivoMove3Asia"], Literal["fr945Asia"], Literal["vivoActive3tChn"], Literal["marqCaptainAsia"], Literal["marqCommanderAsia"], Literal["marqExpeditionAsia"], Literal["marqAthleteAsia"], Literal["indexSmartScale2"], Literal["instinctSolar"], Literal["fr45Asia"], Literal["vivoactive3Daimler"], Literal["legacyRey"], Literal["legacyDarthVader"], Literal["legacyCaptainMarvel"], Literal["legacyFirstAvenger"], Literal["fenix6sSportAsia"], Literal["fenix6sAsia"], Literal["fenix6SportAsia"], Literal["fenix6Asia"], Literal["fenix6xAsia"], Literal["legacyCaptainMarvelAsia"], Literal["legacyFirstAvengerAsia"], Literal["legacyReyAsia"], Literal["legacyDarthVaderAsia"], Literal["descentMk2s"], Literal["edge130Plus"], Literal["edge1030Plus"], Literal["rally200"], Literal["fr745"], Literal["venusqMusic"], Literal["venusqMusicV2"], Literal["venusq"], Literal["lily"], Literal["marqAdventurer"], Literal["enduro"], Literal["swim2Apac"], Literal["marqAdventurerAsia"], Literal["fr945Lte"], Literal["descentMk2Asia"], Literal["venu2"], Literal["venu2s"], Literal["venuDaimlerAsia"], Literal["marqGolfer"], Literal["venuDaimler"], Literal["fr745Asia"], Literal["variaRct715"], Literal["lilyAsia"], Literal["edge1030PlusAsia"], Literal["edge130PlusAsia"], Literal["approachS12"], Literal["enduroAsia"], Literal["venusqAsia"], Literal["edge1040"], Literal["marqGolferAsia"], Literal["venu2Plus"], Literal["gnss"], Literal["fr55"], Literal["instinct2"], Literal["instinct2s"], Literal["fenix7s"], Literal["fenix7"], Literal["fenix7x"], Literal["fenix7sApac"], Literal["fenix7Apac"], Literal["fenix7xApac"], Literal["approachG12"], Literal["descentMk2sAsia"], Literal["approachS42"], Literal["epixGen2"], Literal["epixGen2Apac"], Literal["venu2sAsia"], Literal["venu2Asia"], Literal["fr945LteAsia"], Literal["vivoMoveSport"], Literal["vivomoveTrend"], Literal["approachS12Asia"], Literal["fr255Music"], Literal["fr255SmallMusic"], Literal["fr255"], Literal["fr255Small"], Literal["approachG12Asia"], Literal["approachS42Asia"], Literal["descentG1"], Literal["venu2PlusAsia"], Literal["fr955"], Literal["fr55Asia"], Literal["edge540"], Literal["edge840"], Literal["vivosmart5"], Literal["instinct2Asia"], Literal["marqGen2"], Literal["venusq2"], Literal["venusq2music"], Literal["marqGen2Aviator"], Literal["d2AirX10"], Literal["hrmProPlus"], Literal["descentG1Asia"], Literal["tactix7"], Literal["instinctCrossover"], Literal["edgeExplore2"], Literal["descentMk3"], Literal["descentMk3i"], Literal["approachS70"], Literal["fr265Large"], Literal["fr265Small"], Literal["venu3"], Literal["venu3s"], Literal["tacxNeoSmart"], Literal["tacxNeo2Smart"], Literal["tacxNeo2TSmart"], Literal["tacxNeoSmartBike"], Literal["tacxSatoriSmart"], Literal["tacxFlowSmart"], Literal["tacxVortexSmart"], Literal["tacxBushidoSmart"], Literal["tacxGeniusSmart"], Literal["tacxFluxFluxSSmart"], Literal["tacxFlux2Smart"], Literal["tacxMagnum"], Literal["edge1040Asia"], Literal["epixGen2Pro42"], Literal["epixGen2Pro47"], Literal["epixGen2Pro51"], Literal["fr965"], Literal["enduro2"], Literal["fenix7sProSolar"], Literal["fenix7ProSolar"], Literal["fenix7xProSolar"], Literal["lily2"], Literal["instinct2x"], Literal["vivoactive5"], Literal["fr165"], Literal["fr165Music"], Literal["edge1050"], Literal["descentT2"], Literal["hrmFit"], Literal["marqGen2Commander"], Literal["lilyAthlete"], Literal["rallyX10"], Literal["fenix8Solar"], Literal["fenix8SolarLarge"], Literal["fenix8Small"], Literal["fenix8"], Literal["d2Mach1Pro"], Literal["fr970"], Literal["fr570Large"], Literal["fr570Small"], Literal["enduro3"], Literal["instinctE40mm"], Literal["instinctE45mm"], Literal["instinct3Solar45mm"], Literal["instinct3Amoled45mm"], Literal["instinct3Amoled50mm"], Literal["descentG2"], Literal["fenix7ProSolarNoWifi"], Literal["venuX1"], Literal["hrm200"], Literal["vivoactive6"], Literal["fenix8Pro"], Literal["edge550"], Literal["edge850"], Literal["venu4"], Literal["venu4s"], Literal["approachS44"], Literal["edgeMtb"], Literal["approachS50"], Literal["fenixE"], Literal["bounce2"], Literal["instinct3Solar50mm"], Literal["tactix8Amoled"], Literal["tactix8Solar"], Literal["fr170Music"], Literal["fr170"], Literal["approachJ1"], Literal["approachS7243mm"], Literal["approachS7247mm"], Literal["d2Mach2"], Literal["fr702026"], Literal["fenix9Pro43mm"], Literal["fenix9Pro47mm"], Literal["fenix9Pro51mm"], Literal["fenix9ProSolar47mm"], Literal["fenix9ProSolar51mm"], Literal["fenix943mm"], Literal["fenix9"], Literal["instinctCrossoverAmoled"], Literal["d2AirX15"], Literal["cirqaSmartBand"], Literal["d2Mach2Pro"], Literal["enduro4"], Literal["sdm4"], Literal["edgeRemote"], Literal["tacxTrainingAppWin"], Literal["tacxTrainingAppMac"], Literal["tacxTrainingAppMacCatalyst"], Literal["trainingCenter"], Literal["tacxTrainingAppAndroid"], Literal["tacxTrainingAppIos"], Literal["tacxTrainingAppLegacy"], Literal["trainingPeaksVirtualIos"], Literal["trainingPeaksVirtualAndroid"], Literal["trainingPeaksVirtualWindows"], Literal["trainingPeaksVirtualMacOs"], Literal["trainingPeaksVirtualAppleTv"], Literal["connectiqSimulator"], Literal["androidAntplusPlugin"], Literal["connect"]] AntplusDeviceType = Union[int, Literal["antfs"], Literal["bikePower"], Literal["environmentSensorLegacy"], Literal["multiSportSpeedDistance"], Literal["control"], Literal["fitnessEquipment"], Literal["bloodPressure"], Literal["geocacheNode"], Literal["lightElectricVehicle"], Literal["envSensor"], Literal["racquet"], Literal["controlHub"], Literal["muscleOxygen"], Literal["shifting"], Literal["bikeLightMain"], Literal["bikeLightShared"], Literal["exd"], Literal["bikeRadar"], Literal["bikeAero"], Literal["weightScale"], Literal["heartRate"], Literal["bikeSpeedCadence"], Literal["bikeCadence"], Literal["bikeSpeed"], Literal["strideSpeedDistance"]] diff --git a/garmin_fit_sdk/util.py b/garmin_fit_sdk/util.py index b5339c4..027eb97 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.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ diff --git a/garmin_fit_sdk/util.pyi b/garmin_fit_sdk/util.pyi index a6efd71..feca7d4 100644 --- a/garmin_fit_sdk/util.pyi +++ b/garmin_fit_sdk/util.pyi @@ -5,8 +5,8 @@ # Transfer (FIT) Protocol License. ########################################################################################### # ****WARNING**** This file is auto-generated! Do NOT edit this file. -# Profile Version = 21.217.0Release -# Tag = production/release/21.217.0-0-g248b1c46 +# Profile Version = 21.218.0Release +# Tag = production/release/21.218.0-0-g96fee6d7 ############################################################################################ diff --git a/pyproject.toml b/pyproject.toml index 8675628..4db48b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,12 +3,13 @@ requires = ["setuptools >= 64"] build-backend = "setuptools.build_meta" [project] -version = "21.217.0" +version = "21.218.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" } +license = "LicenseRef-FIT-Protocol-License-Agreement" +license-files = ["LICENSE.txt"] keywords = ["garmin", "fit sdk", "fit"] requires-python = ">=3.6" classifiers = [ @@ -25,11 +26,16 @@ classifiers = [ "Operating System :: OS Independent", "Typing :: Typed", ] -urls = { "Homepage" = "https://github.com/garmin/fit-python-sdk" } dependencies = [ "typing_extensions>=3.7.4; python_version < '3.8'", ] +[project.urls] +Homepage = "https://github.com/garmin/fit-python-sdk" +Documentation = "https://developer.garmin.com/fit/overview/" +Repository = "https://github.com/garmin/fit-python-sdk" +Issues = "https://forums.garmin.com/developer/fit-sdk/f/discussion" + [project.optional-dependencies] dev = ["pytest~=7.0.1", "pytest-cov~=3.0.0", "pytest-mock~=3.6.1"] diff --git a/tests/__init__.py b/tests/__init__.py index 41a615b..41785ee 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -8,4 +8,4 @@ # __init__.py for the fit sdk tests module -__version__ = '21.217.0' \ No newline at end of file +__version__ = '21.218.0' \ No newline at end of file diff --git a/tests/test_encoder.py b/tests/test_encoder.py index d23c47f..59e75ae 100644 --- a/tests/test_encoder.py +++ b/tests/test_encoder.py @@ -151,7 +151,6 @@ def test_transform_value(self, input_value, base_type, field_type, scale, offset assert result == expected @pytest.mark.parametrize('input_value,base_type,field_type,scale,offset,expected', [ - (None, 'UINT8', 'uint8', 1, 0, [0xFF]), ([10, 20, 30], 'UINT16', 'uint16', 1, 0, [10, 20, 30]), ]) def test_transform_values(self, input_value, base_type, field_type, scale, offset, expected): @@ -729,6 +728,34 @@ def test_encode_decode_non_enum_types(self, mesg, field, value, expected_value): assert messages[f'{mesg}_mesgs'] == [{ field: expected_value, }] + def test_encode_decode_different_order_fields(self): + '''Encode a message with fields in a different order and decode it back.''' + messages, errors = _encode_then_decode([ + { + 'mesg_num': Profile['mesg_num']['FILE_ID'], + 'mesg': {'manufacturer': "garmin", 'type': "activity"}, + }, + { + 'mesg_num': Profile['mesg_num']['FILE_ID'], + 'mesg': {'number': 1234, 'manufacturer': "tacx", 'type': "activity"}, + }, + { + 'mesg_num': Profile['mesg_num']['FILE_ID'], + 'mesg': {'type': "activity", 'manufacturer': "garmin"}, + }, + ]) + + assert len(errors) == 0 + assert 'file_id_mesgs' in messages + file_id_mesgs = messages['file_id_mesgs'] + + assert file_id_mesgs[0].get('manufacturer') == "garmin" + assert file_id_mesgs[0].get('type') == "activity" + assert file_id_mesgs[1].get('manufacturer') == "tacx" + assert file_id_mesgs[1].get('number') == 1234 + assert file_id_mesgs[1].get('type') == "activity" + assert file_id_mesgs[2].get('manufacturer') == "garmin" + assert file_id_mesgs[2].get('type') == "activity" # MARK: Encoder Decoder All Types diff --git a/tests/test_mesg_definition.py b/tests/test_mesg_definition.py index 9b862a6..1a6e19e 100644 --- a/tests/test_mesg_definition.py +++ b/tests/test_mesg_definition.py @@ -39,21 +39,72 @@ def test_raises_on_no_valid_fields(self): with pytest.raises(ValueError, match="No valid fields"): _MesgDefinition(0, {'nonexistent_field': 123}) - def test_equals_same_definition(self): - mesg = {'type': 4, 'manufacturer': 1} - def1 = _MesgDefinition(0, mesg) - def2 = _MesgDefinition(0, mesg) - assert def1.equals(def2) - - def test_not_equals_different_fields(self): - def1 = _MesgDefinition(0, {'type': 4, 'manufacturer': 1}) - def2 = _MesgDefinition(0, {'type': 4}) - assert not def1.equals(def2) - - def test_not_equals_different_mesg(self): - def1 = _MesgDefinition(0, {'type': 4}) - def2 = _MesgDefinition(49, {'software_version': 100}) - assert not def1.equals(def2) + _developer_data_id_mesg = {'developer_data_index': 0} + _developer_field_descriptions = { + 'alpha': { + 'developer_data_id_mesg': _developer_data_id_mesg, + 'field_description_mesg': { + 'developer_data_index': 0, + 'field_definition_number': 0, + 'fit_base_type_id': FIT.BASE_TYPE['UINT8'], + }, + }, + 'beta': { + 'developer_data_id_mesg': _developer_data_id_mesg, + 'field_description_mesg': { + 'developer_data_index': 0, + 'field_definition_number': 1, + 'fit_base_type_id': FIT.BASE_TYPE['UINT8'], + }, + }, + } + + @pytest.mark.parametrize( + 'first,second,expected', + [ + pytest.param( + (0, {'type': 4, 'manufacturer': 1}), + (0, {'type': 4, 'manufacturer': 1}), + True, + id='same-definition returns true', + ), + pytest.param( + (0, {'manufacturer': 1, 'type': 4}), + (0, {'type': 1, 'manufacturer': 4}), + True, + id='standard-fields-different-order returns true', + ), + pytest.param( + (0, {'type': 4, 'manufacturer': 1}), + (0, {'type': 4}), + False, + id='different-fields returns false', + ), + pytest.param( + (0, {'type': 4}), + (49, {'software_version': 100}), + False, + id='different-message-types returns false', + ), + pytest.param( + (0, {'type': 4, 'developer_fields': {'alpha': 1, 'beta': 2}}, _developer_field_descriptions), + (0, {'type': 4, 'developer_fields': {'alpha': 3, 'beta': 4}}, _developer_field_descriptions), + True, + id='developer-fields-same-order returns true', + ), + pytest.param( + (0, {'type': 4, 'developer_fields': {'alpha': 1, 'beta': 2}}, _developer_field_descriptions), + (0, {'type': 4, 'developer_fields': {'beta': 4, 'alpha': 3}}, _developer_field_descriptions), + True, + id='developer-fields-different-order returns true', + ), + ], + ) + def test_equals(self, first, second, expected): + first_definition = _MesgDefinition(*first) + second_definition = _MesgDefinition(*second) + + assert first_definition.equals(second_definition) is expected def test_skips_none_values(self): mesg = {'type': 4, 'manufacturer': None}