diff --git a/README.md b/README.md index 9b94f0c..fde406e 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ from fit_tool import ( | `FitFile` | Load, inspect, stream, serialize, and validate FIT files | | `FitFileBuilder` | Build FIT files from messages | | `EncodeMode`, `EncodeOptions` | Explicit encode policies (PRESERVE vs CANONICAL) for `to_bytes` | -| `validate_fit_file`, `ConformanceLevel`, `ValidationReport` | Composable validation (independent of Builder) | +| `validate_fit_file`, `ConformanceLevel`, `ProfileScope`, `ValidationReport`, `profile_rule_coverage` | Composable validation (independent of Builder) | | `FitError` and subclasses | Typed errors for parse, CRC, encode, and validation failures | | `PROTOCOL_VERSION`, `SDK_VERSION`, `FIT_DATA_TYPE` | Bundled protocol/profile version metadata | @@ -220,7 +220,7 @@ Validation is a first-class API. Levels match the design doc | Level | What it checks | Status | | --- | --- | --- | | `ConformanceLevel.WIRE` | Local IDs, definition field layout/sizes, data records vs active definition | Implemented | -| `ConformanceLevel.PROFILE` | Developer field declarations (`developer_data_id` / `field_description`) and base-type consistency; **ambiguous native subfields** (more than one Profile match) as ERROR | **CORE scope today** (+ ambiguous-subfield ERROR). Roadmap: DOMAIN then FULL rules from Profile.xlsx; FULL is opt-in, not default `strict` — design doc §3.1 (O1). Subfield *resolution* for decode/encode is separate and supported | +| `ConformanceLevel.PROFILE` | Scoped Profile semantics via `profile_scope=` / `ProfileScope` | **CORE (default):** developer-field declarations + **ambiguous native subfields** as ERROR. **DOMAIN (opt-in):** CORE + native base-type and closed-enum checks on high-frequency Activity/Workout messages. **FULL (opt-in):** same native rules for the entire gen-exported catalog from Profile.xlsx `21.205.0`. FULL is never default `strict` — design doc §3.1 (O1). Open/ranged enums (e.g. `activity_class`) are excluded from closed-enum checks. | | `ConformanceLevel.FILE_TYPE` | `file_id` first/unique + required fields; Activity, Workout, and Course required messages and fields | **Activity + Workout + Course**; other `file_id.type` values **fail closed** (intentional until more validators exist) | | `ConformanceLevel.PRESERVATION` | Post-edit rewrite loss (e.g. `UnknownField.raw_bytes` cleared by mutation) | **Opt-in** — not in default levels / Builder `strict=True` | diff --git a/fit_tool/__init__.py b/fit_tool/__init__.py index b466f23..b28c0f1 100755 --- a/fit_tool/__init__.py +++ b/fit_tool/__init__.py @@ -29,9 +29,11 @@ FitParseError, FitRecordError, FitValidationError, + ProfileScope, Severity, ValidationFinding, ValidationReport, + profile_rule_coverage, validate_fit_file, ) @@ -51,8 +53,10 @@ 'EncodeMode', 'EncodeOptions', 'ConformanceLevel', + 'ProfileScope', 'Severity', 'ValidationFinding', 'ValidationReport', + 'profile_rule_coverage', 'validate_fit_file', ] diff --git a/fit_tool/api.py b/fit_tool/api.py index f0117ca..dee02eb 100644 --- a/fit_tool/api.py +++ b/fit_tool/api.py @@ -30,9 +30,11 @@ from fit_tool.fit_file_builder import FitFileBuilder from fit_tool.validation import ( ConformanceLevel, + ProfileScope, Severity, ValidationFinding, ValidationReport, + profile_rule_coverage, validate_fit_file, ) @@ -49,8 +51,10 @@ 'EncodeMode', 'EncodeOptions', 'ConformanceLevel', + 'ProfileScope', 'Severity', 'ValidationFinding', 'ValidationReport', + 'profile_rule_coverage', 'validate_fit_file', ] diff --git a/fit_tool/fit_file.py b/fit_tool/fit_file.py index 1799d2c..170fec7 100644 --- a/fit_tool/fit_file.py +++ b/fit_tool/fit_file.py @@ -24,7 +24,7 @@ from fit_tool.wire.model import FitDocument if TYPE_CHECKING: - from fit_tool.validation import ConformanceLevel, ValidationReport + from fit_tool.validation import ConformanceLevel, ProfileScope, ValidationReport # Sentinel so explicit ``check_crc=True`` can override ``options.check_crc=False`` # (a bare default of True is indistinguishable from "caller omitted the kwarg"). @@ -407,6 +407,7 @@ def validate( self, levels: Iterable[ConformanceLevel] | None = None, *, + profile_scope: ProfileScope | None = None, raise_on_error: bool = False, ) -> ValidationReport: """Validate this file at selected conformance levels. @@ -417,8 +418,14 @@ def validate( or include :attr:`~fit_tool.validation.ConformanceLevel.PRESERVATION` for opt-in post-edit rewrite-loss findings. + ``profile_scope`` selects PROFILE depth (CORE default; DOMAIN/FULL opt-in). See :func:`~fit_tool.validation.validate_fit_file` for details. """ from fit_tool.validation import validate_fit_file - return validate_fit_file(self, levels=levels, raise_on_error=raise_on_error) + return validate_fit_file( + self, + levels=levels, + profile_scope=profile_scope, + raise_on_error=raise_on_error, + ) diff --git a/fit_tool/gen/field_catalog.py b/fit_tool/gen/field_catalog.py new file mode 100644 index 0000000..1ebba3d --- /dev/null +++ b/fit_tool/gen/field_catalog.py @@ -0,0 +1,220 @@ +"""Extract native field and enum tables from Profile.xlsx for PROFILE validation. + +Metadata flow (design doc §3.1 O1): Profile.xlsx → gen artifacts → validation. +Runtime validation imports the generated module under ``fit_tool.profile`` and +never loads openpyxl. +""" + +from __future__ import annotations + +from collections.abc import Iterable +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +from fit_tool.base_type import BaseType + + +@dataclass(frozen=True) +class FieldSpec: + """One main (non-subfield) Profile field.""" + + message_name: str + message_number: int + field_id: int + field_name: str + base_type: BaseType + type_name: str + units: str + scale: float + offset: float + + +@dataclass(frozen=True) +class EnumTypeSpec: + """Closed enum type from the Profile Types sheet (base type ENUM only).""" + + type_name: str + values: frozenset[int] + + +@dataclass(frozen=True) +class FieldCatalogData: + """Full native-field + closed-enum catalog for a Profile version.""" + + fields: tuple[FieldSpec, ...] + enum_types: tuple[EnumTypeSpec, ...] + + +def load_field_catalog_from_profile(profile: Any) -> FieldCatalogData: + """Build catalog data from a loaded :class:`~fit_tool.gen.profile.Profile`.""" + fields: list[FieldSpec] = [] + for message in sorted(profile.messages_by_id.values(), key=lambda m: int(m.id)): + for field in sorted( + message.get_fields(), + key=lambda f: int(f.field_id), + ): + type_name = field.type_name or '' + units = field.units or '' + scale = float(field.scale) if field.scale is not None else 1.0 + offset = float(field.offset) if field.offset is not None else 0.0 + fields.append( + FieldSpec( + message_name=str(message.name), + message_number=int(message.id), + field_id=int(field.field_id), + field_name=str(field.name), + base_type=field.base_type, + type_name=str(type_name), + units=str(units), + scale=scale, + offset=offset, + ) + ) + + # ENUM base types that are ranges / bitfields, not closed value sets. + # Example: activity_class uses LEVEL mask 0x7f and ATHLETE flag 0x80. + _OPEN_OR_RANGED_ENUM_TYPES = frozenset({ + 'activity_class', + }) + + enum_types: list[EnumTypeSpec] = [] + for type_name in sorted(profile.types_by_name.keys()): + type_ = profile.types_by_name[type_name] + if type_.base_type is not BaseType.ENUM: + continue + if type_name in _OPEN_OR_RANGED_ENUM_TYPES: + continue + values = getattr(type_, 'values_by_name', None) or {} + if not values: + continue + enum_types.append( + EnumTypeSpec( + type_name=str(type_name), + values=frozenset(int(v) for v in values.values()), + ) + ) + + return FieldCatalogData(fields=tuple(fields), enum_types=tuple(enum_types)) + + +def load_field_catalog(xlsx_path: str | Path) -> FieldCatalogData: + """Load field/enum tables from a Garmin Profile spreadsheet.""" + from fit_tool.gen.profile import Profile + + profile = Profile.load(str(xlsx_path)) + return load_field_catalog_from_profile(profile) + + +def _py_str(value: str) -> str: + return repr(value) + + +def render_field_catalog(data: FieldCatalogData, *, sdk_version: str) -> str: + """Render a Python module defining PROFILE field/enum tables.""" + lines: list[str] = [ + '# Autogenerated. Do not modify.', + '#', + f'# Profile: {sdk_version}', + '"""Native field and closed-enum catalog for PROFILE validation.', + '', + 'Generated from the bundled Garmin Profile spreadsheet by ``gen-profile``.', + '', + '* ``PROFILE_FIELDS`` — ``(global_message_number, field_id)`` → field metadata', + '* ``PROFILE_ENUM_VALUES`` — Profile type name → allowed integer values', + ' (Types sheet entries with base type ``enum`` only)', + '', + 'Used by :mod:`fit_tool.validation` DOMAIN/FULL scopes (design doc §3.1 O1).', + '"""', + '', + 'from __future__ import annotations', + '', + 'from fit_tool.base_type import BaseType', + '', + f'PROFILE_SDK_VERSION = {sdk_version!r}', + f'PROFILE_MESSAGE_COUNT = {len({f.message_number for f in data.fields})}', + f'PROFILE_FIELD_COUNT = {len(data.fields)}', + f'PROFILE_ENUM_TYPE_COUNT = {len(data.enum_types)}', + '', + '# (global_message_number, field_id) → ' + '(name, BaseType, type_name, units, scale, offset)', + 'PROFILE_FIELDS: dict[tuple[int, int], tuple[str, BaseType, str, str, float, float]] = {', + ] + + for field in data.fields: + lines.append( + f' ({field.message_number}, {field.field_id}): (' + f'{_py_str(field.field_name)}, ' + f'BaseType.{field.base_type.name}, ' + f'{_py_str(field.type_name)}, ' + f'{_py_str(field.units)}, ' + f'{field.scale!r}, ' + f'{field.offset!r}' + f'), # {field.message_name}' + ) + + lines.append('}') + lines.append('') + lines.append( + 'PROFILE_FIELD_KEYS: frozenset[tuple[int, int]] = frozenset(PROFILE_FIELDS)' + ) + lines.append('') + lines.append('# Closed enum types only (base type enum). Bitfields / open lists omitted.') + lines.append('PROFILE_ENUM_VALUES: dict[str, frozenset[int]] = {') + + for enum_type in data.enum_types: + values_repr = ', '.join(str(v) for v in sorted(enum_type.values)) + lines.append( + f' {_py_str(enum_type.type_name)}: frozenset({{{values_repr}}}),' + ) + + lines.append('}') + lines.append('') + + # Precompute enum-typed field keys for coverage metrics. + enum_field_count = sum( + 1 + for field in data.fields + if field.type_name and field.type_name in {e.type_name for e in data.enum_types} + ) + lines.append(f'PROFILE_ENUM_FIELD_COUNT = {enum_field_count}') + lines.append('') + return '\n'.join(lines) + '\n' + + +def write_field_catalog( + output_path: str | Path, + *, + xlsx_path: str | Path, + sdk_version: str, +) -> FieldCatalogData: + """Extract catalog from *xlsx_path* and write *output_path*.""" + data = load_field_catalog(xlsx_path) + text = render_field_catalog(data, sdk_version=sdk_version) + path = Path(output_path) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(text, encoding='utf-8') + return data + + +def coverage_summary(data: FieldCatalogData) -> dict[str, Any]: + """High-level coverage stats for docs / tests.""" + messages = sorted({f.message_name for f in data.fields}) + enum_type_names = {e.type_name for e in data.enum_types} + enum_fields = sum(1 for f in data.fields if f.type_name in enum_type_names) + return { + 'messages': len(messages), + 'fields': len(data.fields), + 'enum_types': len(data.enum_types), + 'enum_fields': enum_fields, + 'message_names': messages, + } + + +def domain_message_field_count( + data: FieldCatalogData, + message_numbers: Iterable[int], +) -> int: + """Count main fields belonging to *message_numbers*.""" + wanted = set(message_numbers) + return sum(1 for f in data.fields if f.message_number in wanted) diff --git a/fit_tool/gen/gen_profile.py b/fit_tool/gen/gen_profile.py index f6e2c05..a2c3ca2 100755 --- a/fit_tool/gen/gen_profile.py +++ b/fit_tool/gen/gen_profile.py @@ -8,6 +8,7 @@ from fit_tool.base_type import BaseType from fit_tool.field import Field from fit_tool.gen.component_registry import write_component_registry +from fit_tool.gen.field_catalog import write_field_catalog from fit_tool.gen.profile import Message, Profile DEFAULT_BUILD_PATH = str(Path(__file__).resolve().parents[1]) @@ -318,6 +319,14 @@ def main(): sdk_version=SDK_VERSION, ) + # Native field + closed-enum catalog for PROFILE DOMAIN/FULL (Stage 4 H / O1). + field_catalog_path = os.path.join(profile_path, 'field_catalog.py') + write_field_catalog( + field_catalog_path, + xlsx_path=xlsx_filename, + sdk_version=SDK_VERSION, + ) + if __name__ == "__main__": main() diff --git a/fit_tool/profile/field_catalog.py b/fit_tool/profile/field_catalog.py new file mode 100644 index 0000000..89de9df --- /dev/null +++ b/fit_tool/profile/field_catalog.py @@ -0,0 +1,1541 @@ +# Autogenerated. Do not modify. +# +# Profile: 21.205.0 +"""Native field and closed-enum catalog for PROFILE validation. + +Generated from the bundled Garmin Profile spreadsheet by ``gen-profile``. + +* ``PROFILE_FIELDS`` — ``(global_message_number, field_id)`` → field metadata +* ``PROFILE_ENUM_VALUES`` — Profile type name → allowed integer values + (Types sheet entries with base type ``enum`` only) + +Used by :mod:`fit_tool.validation` DOMAIN/FULL scopes (design doc §3.1 O1). +""" + +from __future__ import annotations + +from fit_tool.base_type import BaseType + +PROFILE_SDK_VERSION = '21.205.0' +PROFILE_MESSAGE_COUNT = 123 +PROFILE_FIELD_COUNT = 1406 +PROFILE_ENUM_TYPE_COUNT = 100 + +# (global_message_number, field_id) → (name, BaseType, type_name, units, scale, offset) +PROFILE_FIELDS: dict[tuple[int, int], tuple[str, BaseType, str, str, float, float]] = { + (0, 0): ('type', BaseType.ENUM, 'file', '', 1.0, 0.0), # file_id + (0, 1): ('manufacturer', BaseType.UINT16, 'manufacturer', '', 1.0, 0.0), # file_id + (0, 2): ('product', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # file_id + (0, 3): ('serial_number', BaseType.UINT32Z, 'uint32z', '', 1.0, 0.0), # file_id + (0, 4): ('time_created', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # file_id + (0, 5): ('number', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # file_id + (0, 8): ('product_name', BaseType.STRING, 'string', '', 1.0, 0.0), # file_id + (1, 0): ('languages', BaseType.UINT8Z, 'uint8z', '', 1.0, 0.0), # capabilities + (1, 1): ('sports', BaseType.UINT8Z, 'sport_bits_0', '', 1.0, 0.0), # capabilities + (1, 21): ('workouts_supported', BaseType.UINT32Z, 'workout_capabilities', '', 1.0, 0.0), # capabilities + (1, 23): ('connectivity_supported', BaseType.UINT32Z, 'connectivity_capabilities', '', 1.0, 0.0), # capabilities + (2, 0): ('active_time_zone', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # device_settings + (2, 1): ('utc_offset', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # device_settings + (2, 2): ('time_offset', BaseType.UINT32, 'uint32', 's', 1.0, 0.0), # device_settings + (2, 4): ('time_mode', BaseType.ENUM, 'time_mode', '', 1.0, 0.0), # device_settings + (2, 5): ('time_zone_offset', BaseType.SINT8, 'sint8', 'hr', 4.0, 0.0), # device_settings + (2, 12): ('backlight_mode', BaseType.ENUM, 'backlight_mode', '', 1.0, 0.0), # device_settings + (2, 36): ('activity_tracker_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # device_settings + (2, 39): ('clock_time', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # device_settings + (2, 40): ('pages_enabled', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # device_settings + (2, 46): ('move_alert_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # device_settings + (2, 47): ('date_mode', BaseType.ENUM, 'date_mode', '', 1.0, 0.0), # device_settings + (2, 55): ('display_orientation', BaseType.ENUM, 'display_orientation', '', 1.0, 0.0), # device_settings + (2, 56): ('mounting_side', BaseType.ENUM, 'side', '', 1.0, 0.0), # device_settings + (2, 57): ('default_page', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # device_settings + (2, 58): ('autosync_min_steps', BaseType.UINT16, 'uint16', 'steps', 1.0, 0.0), # device_settings + (2, 59): ('autosync_min_time', BaseType.UINT16, 'uint16', 'minutes', 1.0, 0.0), # device_settings + (2, 80): ('lactate_threshold_autodetect_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # device_settings + (2, 86): ('ble_auto_upload_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # device_settings + (2, 89): ('auto_sync_frequency', BaseType.ENUM, 'auto_sync_frequency', '', 1.0, 0.0), # device_settings + (2, 90): ('auto_activity_detect', BaseType.UINT32, 'auto_activity_detect', '', 1.0, 0.0), # device_settings + (2, 94): ('number_of_screens', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # device_settings + (2, 95): ('smart_notification_display_orientation', BaseType.ENUM, 'display_orientation', '', 1.0, 0.0), # device_settings + (2, 134): ('tap_interface', BaseType.ENUM, 'switch', '', 1.0, 0.0), # device_settings + (2, 174): ('tap_sensitivity', BaseType.ENUM, 'tap_sensitivity', '', 1.0, 0.0), # device_settings + (3, 0): ('friendly_name', BaseType.STRING, 'string', '', 1.0, 0.0), # user_profile + (3, 1): ('gender', BaseType.ENUM, 'gender', '', 1.0, 0.0), # user_profile + (3, 2): ('age', BaseType.UINT8, 'uint8', 'years', 1.0, 0.0), # user_profile + (3, 3): ('height', BaseType.UINT8, 'uint8', 'm', 100.0, 0.0), # user_profile + (3, 4): ('weight', BaseType.UINT16, 'uint16', 'kg', 10.0, 0.0), # user_profile + (3, 5): ('language', BaseType.ENUM, 'language', '', 1.0, 0.0), # user_profile + (3, 6): ('elev_setting', BaseType.ENUM, 'display_measure', '', 1.0, 0.0), # user_profile + (3, 7): ('weight_setting', BaseType.ENUM, 'display_measure', '', 1.0, 0.0), # user_profile + (3, 8): ('resting_heart_rate', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # user_profile + (3, 9): ('default_max_running_heart_rate', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # user_profile + (3, 10): ('default_max_biking_heart_rate', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # user_profile + (3, 11): ('default_max_heart_rate', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # user_profile + (3, 12): ('hr_setting', BaseType.ENUM, 'display_heart', '', 1.0, 0.0), # user_profile + (3, 13): ('speed_setting', BaseType.ENUM, 'display_measure', '', 1.0, 0.0), # user_profile + (3, 14): ('dist_setting', BaseType.ENUM, 'display_measure', '', 1.0, 0.0), # user_profile + (3, 16): ('power_setting', BaseType.ENUM, 'display_power', '', 1.0, 0.0), # user_profile + (3, 17): ('activity_class', BaseType.ENUM, 'activity_class', '', 1.0, 0.0), # user_profile + (3, 18): ('position_setting', BaseType.ENUM, 'display_position', '', 1.0, 0.0), # user_profile + (3, 21): ('temperature_setting', BaseType.ENUM, 'display_measure', '', 1.0, 0.0), # user_profile + (3, 22): ('local_id', BaseType.UINT16, 'user_local_id', '', 1.0, 0.0), # user_profile + (3, 23): ('global_id', BaseType.BYTE, 'byte', '', 1.0, 0.0), # user_profile + (3, 28): ('wake_time', BaseType.UINT32, 'localtime_into_day', '', 1.0, 0.0), # user_profile + (3, 29): ('sleep_time', BaseType.UINT32, 'localtime_into_day', '', 1.0, 0.0), # user_profile + (3, 30): ('height_setting', BaseType.ENUM, 'display_measure', '', 1.0, 0.0), # user_profile + (3, 31): ('user_running_step_length', BaseType.UINT16, 'uint16', 'm', 1000.0, 0.0), # user_profile + (3, 32): ('user_walking_step_length', BaseType.UINT16, 'uint16', 'm', 1000.0, 0.0), # user_profile + (3, 47): ('depth_setting', BaseType.ENUM, 'display_measure', '', 1.0, 0.0), # user_profile + (3, 49): ('dive_count', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # user_profile + (3, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # user_profile + (4, 0): ('enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # hrm_profile + (4, 1): ('hrm_ant_id', BaseType.UINT16Z, 'uint16z', '', 1.0, 0.0), # hrm_profile + (4, 2): ('log_hrv', BaseType.UINT8, 'bool', '', 1.0, 0.0), # hrm_profile + (4, 3): ('hrm_ant_id_trans_type', BaseType.UINT8Z, 'uint8z', '', 1.0, 0.0), # hrm_profile + (4, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # hrm_profile + (5, 0): ('enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # sdm_profile + (5, 1): ('sdm_ant_id', BaseType.UINT16Z, 'uint16z', '', 1.0, 0.0), # sdm_profile + (5, 2): ('sdm_cal_factor', BaseType.UINT16, 'uint16', '%', 10.0, 0.0), # sdm_profile + (5, 3): ('odometer', BaseType.UINT32, 'uint32', 'm', 100.0, 0.0), # sdm_profile + (5, 4): ('speed_source', BaseType.UINT8, 'bool', '', 1.0, 0.0), # sdm_profile + (5, 5): ('sdm_ant_id_trans_type', BaseType.UINT8Z, 'uint8z', '', 1.0, 0.0), # sdm_profile + (5, 7): ('odometer_rollover', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # sdm_profile + (5, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # sdm_profile + (6, 0): ('name', BaseType.STRING, 'string', '', 1.0, 0.0), # bike_profile + (6, 1): ('sport', BaseType.ENUM, 'sport', '', 1.0, 0.0), # bike_profile + (6, 2): ('sub_sport', BaseType.ENUM, 'sub_sport', '', 1.0, 0.0), # bike_profile + (6, 3): ('odometer', BaseType.UINT32, 'uint32', 'm', 100.0, 0.0), # bike_profile + (6, 4): ('bike_spd_ant_id', BaseType.UINT16Z, 'uint16z', '', 1.0, 0.0), # bike_profile + (6, 5): ('bike_cad_ant_id', BaseType.UINT16Z, 'uint16z', '', 1.0, 0.0), # bike_profile + (6, 6): ('bike_spdcad_ant_id', BaseType.UINT16Z, 'uint16z', '', 1.0, 0.0), # bike_profile + (6, 7): ('bike_power_ant_id', BaseType.UINT16Z, 'uint16z', '', 1.0, 0.0), # bike_profile + (6, 8): ('custom_wheelsize', BaseType.UINT16, 'uint16', 'm', 1000.0, 0.0), # bike_profile + (6, 9): ('auto_wheelsize', BaseType.UINT16, 'uint16', 'm', 1000.0, 0.0), # bike_profile + (6, 10): ('bike_weight', BaseType.UINT16, 'uint16', 'kg', 10.0, 0.0), # bike_profile + (6, 11): ('power_cal_factor', BaseType.UINT16, 'uint16', '%', 10.0, 0.0), # bike_profile + (6, 12): ('auto_wheel_cal', BaseType.UINT8, 'bool', '', 1.0, 0.0), # bike_profile + (6, 13): ('auto_power_zero', BaseType.UINT8, 'bool', '', 1.0, 0.0), # bike_profile + (6, 14): ('id', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # bike_profile + (6, 15): ('spd_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # bike_profile + (6, 16): ('cad_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # bike_profile + (6, 17): ('spdcad_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # bike_profile + (6, 18): ('power_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # bike_profile + (6, 19): ('crank_length', BaseType.UINT8, 'uint8', 'mm', 2.0, -110.0), # bike_profile + (6, 20): ('enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # bike_profile + (6, 21): ('bike_spd_ant_id_trans_type', BaseType.UINT8Z, 'uint8z', '', 1.0, 0.0), # bike_profile + (6, 22): ('bike_cad_ant_id_trans_type', BaseType.UINT8Z, 'uint8z', '', 1.0, 0.0), # bike_profile + (6, 23): ('bike_spdcad_ant_id_trans_type', BaseType.UINT8Z, 'uint8z', '', 1.0, 0.0), # bike_profile + (6, 24): ('bike_power_ant_id_trans_type', BaseType.UINT8Z, 'uint8z', '', 1.0, 0.0), # bike_profile + (6, 37): ('odometer_rollover', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # bike_profile + (6, 38): ('front_gear_num', BaseType.UINT8Z, 'uint8z', '', 1.0, 0.0), # bike_profile + (6, 39): ('front_gear', BaseType.UINT8Z, 'uint8z', '', 1.0, 0.0), # bike_profile + (6, 40): ('rear_gear_num', BaseType.UINT8Z, 'uint8z', '', 1.0, 0.0), # bike_profile + (6, 41): ('rear_gear', BaseType.UINT8Z, 'uint8z', '', 1.0, 0.0), # bike_profile + (6, 44): ('shimano_di2_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # bike_profile + (6, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # bike_profile + (7, 1): ('max_heart_rate', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # zones_target + (7, 2): ('threshold_heart_rate', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # zones_target + (7, 3): ('functional_threshold_power', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # zones_target + (7, 5): ('hr_calc_type', BaseType.ENUM, 'hr_zone_calc', '', 1.0, 0.0), # zones_target + (7, 7): ('pwr_calc_type', BaseType.ENUM, 'pwr_zone_calc', '', 1.0, 0.0), # zones_target + (8, 1): ('high_bpm', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # hr_zone + (8, 2): ('name', BaseType.STRING, 'string', '', 1.0, 0.0), # hr_zone + (8, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # hr_zone + (9, 1): ('high_value', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # power_zone + (9, 2): ('name', BaseType.STRING, 'string', '', 1.0, 0.0), # power_zone + (9, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # power_zone + (10, 1): ('high_bpm', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # met_zone + (10, 2): ('calories', BaseType.UINT16, 'uint16', 'kcal / min', 10.0, 0.0), # met_zone + (10, 3): ('fat_calories', BaseType.UINT8, 'uint8', 'kcal / min', 10.0, 0.0), # met_zone + (10, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # met_zone + (12, 0): ('sport', BaseType.ENUM, 'sport', '', 1.0, 0.0), # sport + (12, 1): ('sub_sport', BaseType.ENUM, 'sub_sport', '', 1.0, 0.0), # sport + (12, 3): ('name', BaseType.STRING, 'string', '', 1.0, 0.0), # sport + (13, 31): ('target_distance', BaseType.UINT32, 'uint32', 'm', 100.0, 0.0), # training_settings + (13, 32): ('target_speed', BaseType.UINT16, 'uint16', 'm/s', 1000.0, 0.0), # training_settings + (13, 33): ('target_time', BaseType.UINT32, 'uint32', 's', 1.0, 0.0), # training_settings + (13, 153): ('precise_target_speed', BaseType.UINT32, 'uint32', 'm/s', 1000000.0, 0.0), # training_settings + (15, 0): ('sport', BaseType.ENUM, 'sport', '', 1.0, 0.0), # goal + (15, 1): ('sub_sport', BaseType.ENUM, 'sub_sport', '', 1.0, 0.0), # goal + (15, 2): ('start_date', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # goal + (15, 3): ('end_date', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # goal + (15, 4): ('type', BaseType.ENUM, 'goal', '', 1.0, 0.0), # goal + (15, 5): ('value', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # goal + (15, 6): ('repeat', BaseType.UINT8, 'bool', '', 1.0, 0.0), # goal + (15, 7): ('target_value', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # goal + (15, 8): ('recurrence', BaseType.ENUM, 'goal_recurrence', '', 1.0, 0.0), # goal + (15, 9): ('recurrence_value', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # goal + (15, 10): ('enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # goal + (15, 11): ('source', BaseType.ENUM, 'goal_source', '', 1.0, 0.0), # goal + (15, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # goal + (18, 0): ('event', BaseType.ENUM, 'event', '', 1.0, 0.0), # session + (18, 1): ('event_type', BaseType.ENUM, 'event_type', '', 1.0, 0.0), # session + (18, 2): ('start_time', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # session + (18, 3): ('start_position_lat', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # session + (18, 4): ('start_position_long', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # session + (18, 5): ('sport', BaseType.ENUM, 'sport', '', 1.0, 0.0), # session + (18, 6): ('sub_sport', BaseType.ENUM, 'sub_sport', '', 1.0, 0.0), # session + (18, 7): ('total_elapsed_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # session + (18, 8): ('total_timer_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # session + (18, 9): ('total_distance', BaseType.UINT32, 'uint32', 'm', 100.0, 0.0), # session + (18, 10): ('total_cycles', BaseType.UINT32, 'uint32', 'cycles', 1.0, 0.0), # session + (18, 11): ('total_calories', BaseType.UINT16, 'uint16', 'kcal', 1.0, 0.0), # session + (18, 13): ('total_fat_calories', BaseType.UINT16, 'uint16', 'kcal', 1.0, 0.0), # session + (18, 14): ('avg_speed', BaseType.UINT16, 'uint16', 'm/s', 1000.0, 0.0), # session + (18, 15): ('max_speed', BaseType.UINT16, 'uint16', 'm/s', 1000.0, 0.0), # session + (18, 16): ('avg_heart_rate', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # session + (18, 17): ('max_heart_rate', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # session + (18, 18): ('avg_cadence', BaseType.UINT8, 'uint8', 'rpm', 1.0, 0.0), # session + (18, 19): ('max_cadence', BaseType.UINT8, 'uint8', 'rpm', 1.0, 0.0), # session + (18, 20): ('avg_power', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # session + (18, 21): ('max_power', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # session + (18, 22): ('total_ascent', BaseType.UINT16, 'uint16', 'm', 1.0, 0.0), # session + (18, 23): ('total_descent', BaseType.UINT16, 'uint16', 'm', 1.0, 0.0), # session + (18, 24): ('total_training_effect', BaseType.UINT8, 'uint8', '', 10.0, 0.0), # session + (18, 25): ('first_lap_index', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # session + (18, 26): ('num_laps', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # session + (18, 27): ('event_group', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # session + (18, 28): ('trigger', BaseType.ENUM, 'session_trigger', '', 1.0, 0.0), # session + (18, 29): ('nec_lat', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # session + (18, 30): ('nec_long', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # session + (18, 31): ('swc_lat', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # session + (18, 32): ('swc_long', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # session + (18, 33): ('num_lengths', BaseType.UINT16, 'uint16', 'lengths', 1.0, 0.0), # session + (18, 34): ('normalized_power', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # session + (18, 35): ('training_stress_score', BaseType.UINT16, 'uint16', 'tss', 10.0, 0.0), # session + (18, 36): ('intensity_factor', BaseType.UINT16, 'uint16', 'if', 1000.0, 0.0), # session + (18, 37): ('left_right_balance', BaseType.UINT16, 'left_right_balance_100', '', 1.0, 0.0), # session + (18, 38): ('end_position_lat', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # session + (18, 39): ('end_position_long', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # session + (18, 41): ('avg_stroke_count', BaseType.UINT32, 'uint32', 'strokes/lap', 10.0, 0.0), # session + (18, 42): ('avg_stroke_distance', BaseType.UINT16, 'uint16', 'm', 100.0, 0.0), # session + (18, 43): ('swim_stroke', BaseType.ENUM, 'swim_stroke', 'swim_stroke', 1.0, 0.0), # session + (18, 44): ('pool_length', BaseType.UINT16, 'uint16', 'm', 100.0, 0.0), # session + (18, 45): ('threshold_power', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # session + (18, 46): ('pool_length_unit', BaseType.ENUM, 'display_measure', '', 1.0, 0.0), # session + (18, 47): ('num_active_lengths', BaseType.UINT16, 'uint16', 'lengths', 1.0, 0.0), # session + (18, 48): ('total_work', BaseType.UINT32, 'uint32', 'J', 1.0, 0.0), # session + (18, 49): ('avg_altitude', BaseType.UINT16, 'uint16', 'm', 5.0, 500.0), # session + (18, 50): ('max_altitude', BaseType.UINT16, 'uint16', 'm', 5.0, 500.0), # session + (18, 51): ('gps_accuracy', BaseType.UINT8, 'uint8', 'm', 1.0, 0.0), # session + (18, 52): ('avg_grade', BaseType.SINT16, 'sint16', '%', 100.0, 0.0), # session + (18, 53): ('avg_pos_grade', BaseType.SINT16, 'sint16', '%', 100.0, 0.0), # session + (18, 54): ('avg_neg_grade', BaseType.SINT16, 'sint16', '%', 100.0, 0.0), # session + (18, 55): ('max_pos_grade', BaseType.SINT16, 'sint16', '%', 100.0, 0.0), # session + (18, 56): ('max_neg_grade', BaseType.SINT16, 'sint16', '%', 100.0, 0.0), # session + (18, 57): ('avg_temperature', BaseType.SINT8, 'sint8', 'C', 1.0, 0.0), # session + (18, 58): ('max_temperature', BaseType.SINT8, 'sint8', 'C', 1.0, 0.0), # session + (18, 59): ('total_moving_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # session + (18, 60): ('avg_pos_vertical_speed', BaseType.SINT16, 'sint16', 'm/s', 1000.0, 0.0), # session + (18, 61): ('avg_neg_vertical_speed', BaseType.SINT16, 'sint16', 'm/s', 1000.0, 0.0), # session + (18, 62): ('max_pos_vertical_speed', BaseType.SINT16, 'sint16', 'm/s', 1000.0, 0.0), # session + (18, 63): ('max_neg_vertical_speed', BaseType.SINT16, 'sint16', 'm/s', 1000.0, 0.0), # session + (18, 64): ('min_heart_rate', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # session + (18, 65): ('time_in_hr_zone', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # session + (18, 66): ('time_in_speed_zone', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # session + (18, 67): ('time_in_cadence_zone', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # session + (18, 68): ('time_in_power_zone', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # session + (18, 69): ('avg_lap_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # session + (18, 70): ('best_lap_index', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # session + (18, 71): ('min_altitude', BaseType.UINT16, 'uint16', 'm', 5.0, 500.0), # session + (18, 78): ('active_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # session + (18, 82): ('player_score', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # session + (18, 83): ('opponent_score', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # session + (18, 84): ('opponent_name', BaseType.STRING, 'string', '', 1.0, 0.0), # session + (18, 85): ('stroke_count', BaseType.UINT16, 'uint16', 'counts', 1.0, 0.0), # session + (18, 86): ('zone_count', BaseType.UINT16, 'uint16', 'counts', 1.0, 0.0), # session + (18, 87): ('max_ball_speed', BaseType.UINT16, 'uint16', 'm/s', 100.0, 0.0), # session + (18, 88): ('avg_ball_speed', BaseType.UINT16, 'uint16', 'm/s', 100.0, 0.0), # session + (18, 89): ('avg_vertical_oscillation', BaseType.UINT16, 'uint16', 'mm', 10.0, 0.0), # session + (18, 90): ('avg_stance_time_percent', BaseType.UINT16, 'uint16', 'percent', 100.0, 0.0), # session + (18, 91): ('avg_stance_time', BaseType.UINT16, 'uint16', 'ms', 10.0, 0.0), # session + (18, 92): ('avg_fractional_cadence', BaseType.UINT8, 'uint8', 'rpm', 128.0, 0.0), # session + (18, 93): ('max_fractional_cadence', BaseType.UINT8, 'uint8', 'rpm', 128.0, 0.0), # session + (18, 94): ('total_fractional_cycles', BaseType.UINT8, 'uint8', 'cycles', 128.0, 0.0), # session + (18, 95): ('avg_total_hemoglobin_conc', BaseType.UINT16, 'uint16', 'g/dL', 100.0, 0.0), # session + (18, 96): ('min_total_hemoglobin_conc', BaseType.UINT16, 'uint16', 'g/dL', 100.0, 0.0), # session + (18, 97): ('max_total_hemoglobin_conc', BaseType.UINT16, 'uint16', 'g/dL', 100.0, 0.0), # session + (18, 98): ('avg_saturated_hemoglobin_percent', BaseType.UINT16, 'uint16', '%', 10.0, 0.0), # session + (18, 99): ('min_saturated_hemoglobin_percent', BaseType.UINT16, 'uint16', '%', 10.0, 0.0), # session + (18, 100): ('max_saturated_hemoglobin_percent', BaseType.UINT16, 'uint16', '%', 10.0, 0.0), # session + (18, 101): ('avg_left_torque_effectiveness', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # session + (18, 102): ('avg_right_torque_effectiveness', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # session + (18, 103): ('avg_left_pedal_smoothness', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # session + (18, 104): ('avg_right_pedal_smoothness', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # session + (18, 105): ('avg_combined_pedal_smoothness', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # session + (18, 110): ('sport_profile_name', BaseType.STRING, 'string', '', 1.0, 0.0), # session + (18, 111): ('sport_index', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # session + (18, 112): ('time_standing', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # session + (18, 113): ('stand_count', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # session + (18, 114): ('avg_left_pco', BaseType.SINT8, 'sint8', 'mm', 1.0, 0.0), # session + (18, 115): ('avg_right_pco', BaseType.SINT8, 'sint8', 'mm', 1.0, 0.0), # session + (18, 116): ('avg_left_power_phase', BaseType.UINT8, 'uint8', 'degrees', 0.7111111, 0.0), # session + (18, 117): ('avg_left_power_phase_peak', BaseType.UINT8, 'uint8', 'degrees', 0.7111111, 0.0), # session + (18, 118): ('avg_right_power_phase', BaseType.UINT8, 'uint8', 'degrees', 0.7111111, 0.0), # session + (18, 119): ('avg_right_power_phase_peak', BaseType.UINT8, 'uint8', 'degrees', 0.7111111, 0.0), # session + (18, 120): ('avg_power_position', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # session + (18, 121): ('max_power_position', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # session + (18, 122): ('avg_cadence_position', BaseType.UINT8, 'uint8', 'rpm', 1.0, 0.0), # session + (18, 123): ('max_cadence_position', BaseType.UINT8, 'uint8', 'rpm', 1.0, 0.0), # session + (18, 124): ('enhanced_avg_speed', BaseType.UINT32, 'uint32', 'm/s', 1000.0, 0.0), # session + (18, 125): ('enhanced_max_speed', BaseType.UINT32, 'uint32', 'm/s', 1000.0, 0.0), # session + (18, 126): ('enhanced_avg_altitude', BaseType.UINT32, 'uint32', 'm', 5.0, 500.0), # session + (18, 127): ('enhanced_min_altitude', BaseType.UINT32, 'uint32', 'm', 5.0, 500.0), # session + (18, 128): ('enhanced_max_altitude', BaseType.UINT32, 'uint32', 'm', 5.0, 500.0), # session + (18, 129): ('avg_lev_motor_power', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # session + (18, 130): ('max_lev_motor_power', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # session + (18, 131): ('lev_battery_consumption', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # session + (18, 132): ('avg_vertical_ratio', BaseType.UINT16, 'uint16', 'percent', 100.0, 0.0), # session + (18, 133): ('avg_stance_time_balance', BaseType.UINT16, 'uint16', 'percent', 100.0, 0.0), # session + (18, 134): ('avg_step_length', BaseType.UINT16, 'uint16', 'mm', 10.0, 0.0), # session + (18, 137): ('total_anaerobic_training_effect', BaseType.UINT8, 'uint8', '', 10.0, 0.0), # session + (18, 139): ('avg_vam', BaseType.UINT16, 'uint16', 'm/s', 1000.0, 0.0), # session + (18, 140): ('avg_depth', BaseType.UINT32, 'uint32', 'm', 1000.0, 0.0), # session + (18, 141): ('max_depth', BaseType.UINT32, 'uint32', 'm', 1000.0, 0.0), # session + (18, 142): ('surface_interval', BaseType.UINT32, 'uint32', 's', 1.0, 0.0), # session + (18, 143): ('start_cns', BaseType.UINT8, 'uint8', 'percent', 1.0, 0.0), # session + (18, 144): ('end_cns', BaseType.UINT8, 'uint8', 'percent', 1.0, 0.0), # session + (18, 145): ('start_n2', BaseType.UINT16, 'uint16', 'percent', 1.0, 0.0), # session + (18, 146): ('end_n2', BaseType.UINT16, 'uint16', 'percent', 1.0, 0.0), # session + (18, 147): ('avg_respiration_rate', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # session + (18, 148): ('max_respiration_rate', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # session + (18, 149): ('min_respiration_rate', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # session + (18, 150): ('min_temperature', BaseType.SINT8, 'sint8', 'C', 1.0, 0.0), # session + (18, 155): ('o2_toxicity', BaseType.UINT16, 'uint16', 'OTUs', 1.0, 0.0), # session + (18, 156): ('dive_number', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # session + (18, 168): ('training_load_peak', BaseType.SINT32, 'sint32', '', 65536.0, 0.0), # session + (18, 169): ('enhanced_avg_respiration_rate', BaseType.UINT16, 'uint16', 'Breaths/min', 100.0, 0.0), # session + (18, 170): ('enhanced_max_respiration_rate', BaseType.UINT16, 'uint16', 'Breaths/min', 100.0, 0.0), # session + (18, 180): ('enhanced_min_respiration_rate', BaseType.UINT16, 'uint16', '', 100.0, 0.0), # session + (18, 181): ('total_grit', BaseType.FLOAT32, 'float32', 'kGrit', 1.0, 0.0), # session + (18, 182): ('total_flow', BaseType.FLOAT32, 'float32', 'Flow', 1.0, 0.0), # session + (18, 183): ('jump_count', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # session + (18, 186): ('avg_grit', BaseType.FLOAT32, 'float32', 'kGrit', 1.0, 0.0), # session + (18, 187): ('avg_flow', BaseType.FLOAT32, 'float32', 'Flow', 1.0, 0.0), # session + (18, 192): ('workout_feel', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # session + (18, 193): ('workout_rpe', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # session + (18, 194): ('avg_spo2', BaseType.UINT8, 'uint8', 'percent', 1.0, 0.0), # session + (18, 195): ('avg_stress', BaseType.UINT8, 'uint8', 'percent', 1.0, 0.0), # session + (18, 196): ('metabolic_calories', BaseType.UINT16, 'uint16', 'kcal', 1.0, 0.0), # session + (18, 197): ('sdrr_hrv', BaseType.UINT8, 'uint8', 'mS', 1.0, 0.0), # session + (18, 198): ('rmssd_hrv', BaseType.UINT8, 'uint8', 'mS', 1.0, 0.0), # session + (18, 199): ('total_fractional_ascent', BaseType.UINT8, 'uint8', 'm', 100.0, 0.0), # session + (18, 200): ('total_fractional_descent', BaseType.UINT8, 'uint8', 'm', 100.0, 0.0), # session + (18, 208): ('avg_core_temperature', BaseType.UINT16, 'uint16', 'C', 100.0, 0.0), # session + (18, 209): ('min_core_temperature', BaseType.UINT16, 'uint16', 'C', 100.0, 0.0), # session + (18, 210): ('max_core_temperature', BaseType.UINT16, 'uint16', 'C', 100.0, 0.0), # session + (18, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # session + (18, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # session + (19, 0): ('event', BaseType.ENUM, 'event', '', 1.0, 0.0), # lap + (19, 1): ('event_type', BaseType.ENUM, 'event_type', '', 1.0, 0.0), # lap + (19, 2): ('start_time', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # lap + (19, 3): ('start_position_lat', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # lap + (19, 4): ('start_position_long', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # lap + (19, 5): ('end_position_lat', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # lap + (19, 6): ('end_position_long', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # lap + (19, 7): ('total_elapsed_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # lap + (19, 8): ('total_timer_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # lap + (19, 9): ('total_distance', BaseType.UINT32, 'uint32', 'm', 100.0, 0.0), # lap + (19, 10): ('total_cycles', BaseType.UINT32, 'uint32', 'cycles', 1.0, 0.0), # lap + (19, 11): ('total_calories', BaseType.UINT16, 'uint16', 'kcal', 1.0, 0.0), # lap + (19, 12): ('total_fat_calories', BaseType.UINT16, 'uint16', 'kcal', 1.0, 0.0), # lap + (19, 13): ('avg_speed', BaseType.UINT16, 'uint16', 'm/s', 1000.0, 0.0), # lap + (19, 14): ('max_speed', BaseType.UINT16, 'uint16', 'm/s', 1000.0, 0.0), # lap + (19, 15): ('avg_heart_rate', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # lap + (19, 16): ('max_heart_rate', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # lap + (19, 17): ('avg_cadence', BaseType.UINT8, 'uint8', 'rpm', 1.0, 0.0), # lap + (19, 18): ('max_cadence', BaseType.UINT8, 'uint8', 'rpm', 1.0, 0.0), # lap + (19, 19): ('avg_power', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # lap + (19, 20): ('max_power', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # lap + (19, 21): ('total_ascent', BaseType.UINT16, 'uint16', 'm', 1.0, 0.0), # lap + (19, 22): ('total_descent', BaseType.UINT16, 'uint16', 'm', 1.0, 0.0), # lap + (19, 23): ('intensity', BaseType.ENUM, 'intensity', '', 1.0, 0.0), # lap + (19, 24): ('lap_trigger', BaseType.ENUM, 'lap_trigger', '', 1.0, 0.0), # lap + (19, 25): ('sport', BaseType.ENUM, 'sport', '', 1.0, 0.0), # lap + (19, 26): ('event_group', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # lap + (19, 32): ('num_lengths', BaseType.UINT16, 'uint16', 'lengths', 1.0, 0.0), # lap + (19, 33): ('normalized_power', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # lap + (19, 34): ('left_right_balance', BaseType.UINT16, 'left_right_balance_100', '', 1.0, 0.0), # lap + (19, 35): ('first_length_index', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # lap + (19, 37): ('avg_stroke_distance', BaseType.UINT16, 'uint16', 'm', 100.0, 0.0), # lap + (19, 38): ('swim_stroke', BaseType.ENUM, 'swim_stroke', '', 1.0, 0.0), # lap + (19, 39): ('sub_sport', BaseType.ENUM, 'sub_sport', '', 1.0, 0.0), # lap + (19, 40): ('num_active_lengths', BaseType.UINT16, 'uint16', 'lengths', 1.0, 0.0), # lap + (19, 41): ('total_work', BaseType.UINT32, 'uint32', 'J', 1.0, 0.0), # lap + (19, 42): ('avg_altitude', BaseType.UINT16, 'uint16', 'm', 5.0, 500.0), # lap + (19, 43): ('max_altitude', BaseType.UINT16, 'uint16', 'm', 5.0, 500.0), # lap + (19, 44): ('gps_accuracy', BaseType.UINT8, 'uint8', 'm', 1.0, 0.0), # lap + (19, 45): ('avg_grade', BaseType.SINT16, 'sint16', '%', 100.0, 0.0), # lap + (19, 46): ('avg_pos_grade', BaseType.SINT16, 'sint16', '%', 100.0, 0.0), # lap + (19, 47): ('avg_neg_grade', BaseType.SINT16, 'sint16', '%', 100.0, 0.0), # lap + (19, 48): ('max_pos_grade', BaseType.SINT16, 'sint16', '%', 100.0, 0.0), # lap + (19, 49): ('max_neg_grade', BaseType.SINT16, 'sint16', '%', 100.0, 0.0), # lap + (19, 50): ('avg_temperature', BaseType.SINT8, 'sint8', 'C', 1.0, 0.0), # lap + (19, 51): ('max_temperature', BaseType.SINT8, 'sint8', 'C', 1.0, 0.0), # lap + (19, 52): ('total_moving_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # lap + (19, 53): ('avg_pos_vertical_speed', BaseType.SINT16, 'sint16', 'm/s', 1000.0, 0.0), # lap + (19, 54): ('avg_neg_vertical_speed', BaseType.SINT16, 'sint16', 'm/s', 1000.0, 0.0), # lap + (19, 55): ('max_pos_vertical_speed', BaseType.SINT16, 'sint16', 'm/s', 1000.0, 0.0), # lap + (19, 56): ('max_neg_vertical_speed', BaseType.SINT16, 'sint16', 'm/s', 1000.0, 0.0), # lap + (19, 57): ('time_in_hr_zone', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # lap + (19, 58): ('time_in_speed_zone', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # lap + (19, 59): ('time_in_cadence_zone', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # lap + (19, 60): ('time_in_power_zone', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # lap + (19, 61): ('repetition_num', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # lap + (19, 62): ('min_altitude', BaseType.UINT16, 'uint16', 'm', 5.0, 500.0), # lap + (19, 63): ('min_heart_rate', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # lap + (19, 70): ('active_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # lap + (19, 71): ('wkt_step_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # lap + (19, 74): ('opponent_score', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # lap + (19, 75): ('stroke_count', BaseType.UINT16, 'uint16', 'counts', 1.0, 0.0), # lap + (19, 76): ('zone_count', BaseType.UINT16, 'uint16', 'counts', 1.0, 0.0), # lap + (19, 77): ('avg_vertical_oscillation', BaseType.UINT16, 'uint16', 'mm', 10.0, 0.0), # lap + (19, 78): ('avg_stance_time_percent', BaseType.UINT16, 'uint16', 'percent', 100.0, 0.0), # lap + (19, 79): ('avg_stance_time', BaseType.UINT16, 'uint16', 'ms', 10.0, 0.0), # lap + (19, 80): ('avg_fractional_cadence', BaseType.UINT8, 'uint8', 'rpm', 128.0, 0.0), # lap + (19, 81): ('max_fractional_cadence', BaseType.UINT8, 'uint8', 'rpm', 128.0, 0.0), # lap + (19, 82): ('total_fractional_cycles', BaseType.UINT8, 'uint8', 'cycles', 128.0, 0.0), # lap + (19, 83): ('player_score', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # lap + (19, 84): ('avg_total_hemoglobin_conc', BaseType.UINT16, 'uint16', 'g/dL', 100.0, 0.0), # lap + (19, 85): ('min_total_hemoglobin_conc', BaseType.UINT16, 'uint16', 'g/dL', 100.0, 0.0), # lap + (19, 86): ('max_total_hemoglobin_conc', BaseType.UINT16, 'uint16', 'g/dL', 100.0, 0.0), # lap + (19, 87): ('avg_saturated_hemoglobin_percent', BaseType.UINT16, 'uint16', '%', 10.0, 0.0), # lap + (19, 88): ('min_saturated_hemoglobin_percent', BaseType.UINT16, 'uint16', '%', 10.0, 0.0), # lap + (19, 89): ('max_saturated_hemoglobin_percent', BaseType.UINT16, 'uint16', '%', 10.0, 0.0), # lap + (19, 91): ('avg_left_torque_effectiveness', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # lap + (19, 92): ('avg_right_torque_effectiveness', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # lap + (19, 93): ('avg_left_pedal_smoothness', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # lap + (19, 94): ('avg_right_pedal_smoothness', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # lap + (19, 95): ('avg_combined_pedal_smoothness', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # lap + (19, 98): ('time_standing', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # lap + (19, 99): ('stand_count', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # lap + (19, 100): ('avg_left_pco', BaseType.SINT8, 'sint8', 'mm', 1.0, 0.0), # lap + (19, 101): ('avg_right_pco', BaseType.SINT8, 'sint8', 'mm', 1.0, 0.0), # lap + (19, 102): ('avg_left_power_phase', BaseType.UINT8, 'uint8', 'degrees', 0.7111111, 0.0), # lap + (19, 103): ('avg_left_power_phase_peak', BaseType.UINT8, 'uint8', 'degrees', 0.7111111, 0.0), # lap + (19, 104): ('avg_right_power_phase', BaseType.UINT8, 'uint8', 'degrees', 0.7111111, 0.0), # lap + (19, 105): ('avg_right_power_phase_peak', BaseType.UINT8, 'uint8', 'degrees', 0.7111111, 0.0), # lap + (19, 106): ('avg_power_position', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # lap + (19, 107): ('max_power_position', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # lap + (19, 108): ('avg_cadence_position', BaseType.UINT8, 'uint8', 'rpm', 1.0, 0.0), # lap + (19, 109): ('max_cadence_position', BaseType.UINT8, 'uint8', 'rpm', 1.0, 0.0), # lap + (19, 110): ('enhanced_avg_speed', BaseType.UINT32, 'uint32', 'm/s', 1000.0, 0.0), # lap + (19, 111): ('enhanced_max_speed', BaseType.UINT32, 'uint32', 'm/s', 1000.0, 0.0), # lap + (19, 112): ('enhanced_avg_altitude', BaseType.UINT32, 'uint32', 'm', 5.0, 500.0), # lap + (19, 113): ('enhanced_min_altitude', BaseType.UINT32, 'uint32', 'm', 5.0, 500.0), # lap + (19, 114): ('enhanced_max_altitude', BaseType.UINT32, 'uint32', 'm', 5.0, 500.0), # lap + (19, 115): ('avg_lev_motor_power', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # lap + (19, 116): ('max_lev_motor_power', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # lap + (19, 117): ('lev_battery_consumption', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # lap + (19, 118): ('avg_vertical_ratio', BaseType.UINT16, 'uint16', 'percent', 100.0, 0.0), # lap + (19, 119): ('avg_stance_time_balance', BaseType.UINT16, 'uint16', 'percent', 100.0, 0.0), # lap + (19, 120): ('avg_step_length', BaseType.UINT16, 'uint16', 'mm', 10.0, 0.0), # lap + (19, 121): ('avg_vam', BaseType.UINT16, 'uint16', 'm/s', 1000.0, 0.0), # lap + (19, 122): ('avg_depth', BaseType.UINT32, 'uint32', 'm', 1000.0, 0.0), # lap + (19, 123): ('max_depth', BaseType.UINT32, 'uint32', 'm', 1000.0, 0.0), # lap + (19, 124): ('min_temperature', BaseType.SINT8, 'sint8', 'C', 1.0, 0.0), # lap + (19, 136): ('enhanced_avg_respiration_rate', BaseType.UINT16, 'uint16', 'Breaths/min', 100.0, 0.0), # lap + (19, 137): ('enhanced_max_respiration_rate', BaseType.UINT16, 'uint16', 'Breaths/min', 100.0, 0.0), # lap + (19, 147): ('avg_respiration_rate', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # lap + (19, 148): ('max_respiration_rate', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # lap + (19, 149): ('total_grit', BaseType.FLOAT32, 'float32', 'kGrit', 1.0, 0.0), # lap + (19, 150): ('total_flow', BaseType.FLOAT32, 'float32', 'Flow', 1.0, 0.0), # lap + (19, 151): ('jump_count', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # lap + (19, 153): ('avg_grit', BaseType.FLOAT32, 'float32', 'kGrit', 1.0, 0.0), # lap + (19, 154): ('avg_flow', BaseType.FLOAT32, 'float32', 'Flow', 1.0, 0.0), # lap + (19, 156): ('total_fractional_ascent', BaseType.UINT8, 'uint8', 'm', 100.0, 0.0), # lap + (19, 157): ('total_fractional_descent', BaseType.UINT8, 'uint8', 'm', 100.0, 0.0), # lap + (19, 158): ('avg_core_temperature', BaseType.UINT16, 'uint16', 'C', 100.0, 0.0), # lap + (19, 159): ('min_core_temperature', BaseType.UINT16, 'uint16', 'C', 100.0, 0.0), # lap + (19, 160): ('max_core_temperature', BaseType.UINT16, 'uint16', 'C', 100.0, 0.0), # lap + (19, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # lap + (19, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # lap + (20, 0): ('position_lat', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # record + (20, 1): ('position_long', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # record + (20, 2): ('altitude', BaseType.UINT16, 'uint16', 'm', 5.0, 500.0), # record + (20, 3): ('heart_rate', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # record + (20, 4): ('cadence', BaseType.UINT8, 'uint8', 'rpm', 1.0, 0.0), # record + (20, 5): ('distance', BaseType.UINT32, 'uint32', 'm', 100.0, 0.0), # record + (20, 6): ('speed', BaseType.UINT16, 'uint16', 'm/s', 1000.0, 0.0), # record + (20, 7): ('power', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # record + (20, 8): ('compressed_speed_distance', BaseType.BYTE, 'byte', 'm/s,m', 1.0, 0.0), # record + (20, 9): ('grade', BaseType.SINT16, 'sint16', '%', 100.0, 0.0), # record + (20, 10): ('resistance', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # record + (20, 11): ('time_from_course', BaseType.SINT32, 'sint32', 's', 1000.0, 0.0), # record + (20, 12): ('cycle_length', BaseType.UINT8, 'uint8', 'm', 100.0, 0.0), # record + (20, 13): ('temperature', BaseType.SINT8, 'sint8', 'C', 1.0, 0.0), # record + (20, 17): ('speed_1s', BaseType.UINT8, 'uint8', 'm/s', 16.0, 0.0), # record + (20, 18): ('cycles', BaseType.UINT8, 'uint8', 'cycles', 1.0, 0.0), # record + (20, 19): ('total_cycles', BaseType.UINT32, 'uint32', 'cycles', 1.0, 0.0), # record + (20, 28): ('compressed_accumulated_power', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # record + (20, 29): ('accumulated_power', BaseType.UINT32, 'uint32', 'watts', 1.0, 0.0), # record + (20, 30): ('left_right_balance', BaseType.UINT8, 'left_right_balance', '', 1.0, 0.0), # record + (20, 31): ('gps_accuracy', BaseType.UINT8, 'uint8', 'm', 1.0, 0.0), # record + (20, 32): ('vertical_speed', BaseType.SINT16, 'sint16', 'm/s', 1000.0, 0.0), # record + (20, 33): ('calories', BaseType.UINT16, 'uint16', 'kcal', 1.0, 0.0), # record + (20, 39): ('vertical_oscillation', BaseType.UINT16, 'uint16', 'mm', 10.0, 0.0), # record + (20, 40): ('stance_time_percent', BaseType.UINT16, 'uint16', 'percent', 100.0, 0.0), # record + (20, 41): ('stance_time', BaseType.UINT16, 'uint16', 'ms', 10.0, 0.0), # record + (20, 42): ('activity_type', BaseType.ENUM, 'activity_type', '', 1.0, 0.0), # record + (20, 43): ('left_torque_effectiveness', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # record + (20, 44): ('right_torque_effectiveness', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # record + (20, 45): ('left_pedal_smoothness', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # record + (20, 46): ('right_pedal_smoothness', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # record + (20, 47): ('combined_pedal_smoothness', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # record + (20, 48): ('time128', BaseType.UINT8, 'uint8', 's', 128.0, 0.0), # record + (20, 49): ('stroke_type', BaseType.ENUM, 'stroke_type', '', 1.0, 0.0), # record + (20, 50): ('zone', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # record + (20, 51): ('ball_speed', BaseType.UINT16, 'uint16', 'm/s', 100.0, 0.0), # record + (20, 52): ('cadence256', BaseType.UINT16, 'uint16', 'rpm', 256.0, 0.0), # record + (20, 53): ('fractional_cadence', BaseType.UINT8, 'uint8', 'rpm', 128.0, 0.0), # record + (20, 54): ('total_hemoglobin_conc', BaseType.UINT16, 'uint16', 'g/dL', 100.0, 0.0), # record + (20, 55): ('total_hemoglobin_conc_min', BaseType.UINT16, 'uint16', 'g/dL', 100.0, 0.0), # record + (20, 56): ('total_hemoglobin_conc_max', BaseType.UINT16, 'uint16', 'g/dL', 100.0, 0.0), # record + (20, 57): ('saturated_hemoglobin_percent', BaseType.UINT16, 'uint16', '%', 10.0, 0.0), # record + (20, 58): ('saturated_hemoglobin_percent_min', BaseType.UINT16, 'uint16', '%', 10.0, 0.0), # record + (20, 59): ('saturated_hemoglobin_percent_max', BaseType.UINT16, 'uint16', '%', 10.0, 0.0), # record + (20, 62): ('device_index', BaseType.UINT8, 'device_index', '', 1.0, 0.0), # record + (20, 67): ('left_pco', BaseType.SINT8, 'sint8', 'mm', 1.0, 0.0), # record + (20, 68): ('right_pco', BaseType.SINT8, 'sint8', 'mm', 1.0, 0.0), # record + (20, 69): ('left_power_phase', BaseType.UINT8, 'uint8', 'degrees', 0.7111111, 0.0), # record + (20, 70): ('left_power_phase_peak', BaseType.UINT8, 'uint8', 'degrees', 0.7111111, 0.0), # record + (20, 71): ('right_power_phase', BaseType.UINT8, 'uint8', 'degrees', 0.7111111, 0.0), # record + (20, 72): ('right_power_phase_peak', BaseType.UINT8, 'uint8', 'degrees', 0.7111111, 0.0), # record + (20, 73): ('enhanced_speed', BaseType.UINT32, 'uint32', 'm/s', 1000.0, 0.0), # record + (20, 78): ('enhanced_altitude', BaseType.UINT32, 'uint32', 'm', 5.0, 500.0), # record + (20, 81): ('battery_soc', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # record + (20, 82): ('motor_power', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # record + (20, 83): ('vertical_ratio', BaseType.UINT16, 'uint16', 'percent', 100.0, 0.0), # record + (20, 84): ('stance_time_balance', BaseType.UINT16, 'uint16', 'percent', 100.0, 0.0), # record + (20, 85): ('step_length', BaseType.UINT16, 'uint16', 'mm', 10.0, 0.0), # record + (20, 87): ('cycle_length16', BaseType.UINT16, 'uint16', 'm', 100.0, 0.0), # record + (20, 91): ('absolute_pressure', BaseType.UINT32, 'uint32', 'Pa', 1.0, 0.0), # record + (20, 92): ('depth', BaseType.UINT32, 'uint32', 'm', 1000.0, 0.0), # record + (20, 93): ('next_stop_depth', BaseType.UINT32, 'uint32', 'm', 1000.0, 0.0), # record + (20, 94): ('next_stop_time', BaseType.UINT32, 'uint32', 's', 1.0, 0.0), # record + (20, 95): ('time_to_surface', BaseType.UINT32, 'uint32', 's', 1.0, 0.0), # record + (20, 96): ('ndl_time', BaseType.UINT32, 'uint32', 's', 1.0, 0.0), # record + (20, 97): ('cns_load', BaseType.UINT8, 'uint8', 'percent', 1.0, 0.0), # record + (20, 98): ('n2_load', BaseType.UINT16, 'uint16', 'percent', 1.0, 0.0), # record + (20, 99): ('respiration_rate', BaseType.UINT8, 'uint8', 's', 1.0, 0.0), # record + (20, 108): ('enhanced_respiration_rate', BaseType.UINT16, 'uint16', 'Breaths/min', 100.0, 0.0), # record + (20, 114): ('grit', BaseType.FLOAT32, 'float32', '', 1.0, 0.0), # record + (20, 115): ('flow', BaseType.FLOAT32, 'float32', '', 1.0, 0.0), # record + (20, 116): ('current_stress', BaseType.UINT16, 'uint16', '', 100.0, 0.0), # record + (20, 117): ('ebike_travel_range', BaseType.UINT16, 'uint16', 'km', 1.0, 0.0), # record + (20, 118): ('ebike_battery_level', BaseType.UINT8, 'uint8', 'percent', 1.0, 0.0), # record + (20, 119): ('ebike_assist_mode', BaseType.UINT8, 'uint8', 'depends on sensor', 1.0, 0.0), # record + (20, 120): ('ebike_assist_level_percent', BaseType.UINT8, 'uint8', 'percent', 1.0, 0.0), # record + (20, 123): ('air_time_remaining', BaseType.UINT32, 'uint32', 's', 1.0, 0.0), # record + (20, 124): ('pressure_sac', BaseType.UINT16, 'uint16', 'bar/min', 100.0, 0.0), # record + (20, 125): ('volume_sac', BaseType.UINT16, 'uint16', 'L/min', 100.0, 0.0), # record + (20, 126): ('rmv', BaseType.UINT16, 'uint16', 'L/min', 100.0, 0.0), # record + (20, 127): ('ascent_rate', BaseType.SINT32, 'sint32', 'm/s', 1000.0, 0.0), # record + (20, 129): ('po2', BaseType.UINT8, 'uint8', 'percent', 100.0, 0.0), # record + (20, 139): ('core_temperature', BaseType.UINT16, 'uint16', 'C', 100.0, 0.0), # record + (20, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # record + (21, 0): ('event', BaseType.ENUM, 'event', '', 1.0, 0.0), # event + (21, 1): ('event_type', BaseType.ENUM, 'event_type', '', 1.0, 0.0), # event + (21, 2): ('data16', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # event + (21, 3): ('data', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # event + (21, 4): ('event_group', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # event + (21, 7): ('score', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # event + (21, 8): ('opponent_score', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # event + (21, 9): ('front_gear_num', BaseType.UINT8Z, 'uint8z', '', 1.0, 0.0), # event + (21, 10): ('front_gear', BaseType.UINT8Z, 'uint8z', '', 1.0, 0.0), # event + (21, 11): ('rear_gear_num', BaseType.UINT8Z, 'uint8z', '', 1.0, 0.0), # event + (21, 12): ('rear_gear', BaseType.UINT8Z, 'uint8z', '', 1.0, 0.0), # event + (21, 13): ('device_index', BaseType.UINT8, 'device_index', '', 1.0, 0.0), # event + (21, 14): ('activity_type', BaseType.ENUM, 'activity_type', '', 1.0, 0.0), # event + (21, 15): ('start_timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # event + (21, 21): ('radar_threat_level_max', BaseType.ENUM, 'radar_threat_level_type', '', 1.0, 0.0), # event + (21, 22): ('radar_threat_count', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # event + (21, 23): ('radar_threat_avg_approach_speed', BaseType.UINT8, 'uint8', 'm/s', 10.0, 0.0), # event + (21, 24): ('radar_threat_max_approach_speed', BaseType.UINT8, 'uint8', 'm/s', 10.0, 0.0), # event + (21, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # event + (23, 0): ('device_index', BaseType.UINT8, 'device_index', '', 1.0, 0.0), # device_info + (23, 1): ('device_type', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # device_info + (23, 2): ('manufacturer', BaseType.UINT16, 'manufacturer', '', 1.0, 0.0), # device_info + (23, 3): ('serial_number', BaseType.UINT32Z, 'uint32z', '', 1.0, 0.0), # device_info + (23, 4): ('product', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # device_info + (23, 5): ('software_version', BaseType.UINT16, 'uint16', '', 100.0, 0.0), # device_info + (23, 6): ('hardware_version', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # device_info + (23, 7): ('cum_operating_time', BaseType.UINT32, 'uint32', 's', 1.0, 0.0), # device_info + (23, 10): ('battery_voltage', BaseType.UINT16, 'uint16', 'V', 256.0, 0.0), # device_info + (23, 11): ('battery_status', BaseType.UINT8, 'battery_status', '', 1.0, 0.0), # device_info + (23, 18): ('sensor_position', BaseType.ENUM, 'body_location', '', 1.0, 0.0), # device_info + (23, 19): ('descriptor', BaseType.STRING, 'string', '', 1.0, 0.0), # device_info + (23, 20): ('ant_transmission_type', BaseType.UINT8Z, 'uint8z', '', 1.0, 0.0), # device_info + (23, 21): ('ant_device_number', BaseType.UINT16Z, 'uint16z', '', 1.0, 0.0), # device_info + (23, 22): ('ant_network', BaseType.ENUM, 'ant_network', '', 1.0, 0.0), # device_info + (23, 25): ('source_type', BaseType.ENUM, 'source_type', '', 1.0, 0.0), # device_info + (23, 27): ('product_name', BaseType.STRING, 'string', '', 1.0, 0.0), # device_info + (23, 32): ('battery_level', BaseType.UINT8, 'uint8', '%', 1.0, 0.0), # device_info + (23, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # device_info + (26, 4): ('sport', BaseType.ENUM, 'sport', '', 1.0, 0.0), # workout + (26, 5): ('capabilities', BaseType.UINT32Z, 'workout_capabilities', '', 1.0, 0.0), # workout + (26, 6): ('num_valid_steps', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # workout + (26, 8): ('wkt_name', BaseType.STRING, 'string', '', 1.0, 0.0), # workout + (26, 11): ('sub_sport', BaseType.ENUM, 'sub_sport', '', 1.0, 0.0), # workout + (26, 14): ('pool_length', BaseType.UINT16, 'uint16', 'm', 100.0, 0.0), # workout + (26, 15): ('pool_length_unit', BaseType.ENUM, 'display_measure', '', 1.0, 0.0), # workout + (26, 17): ('wkt_description', BaseType.STRING, 'string', '', 1.0, 0.0), # workout + (26, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # workout + (27, 0): ('wkt_step_name', BaseType.STRING, 'string', '', 1.0, 0.0), # workout_step + (27, 1): ('duration_type', BaseType.ENUM, 'wkt_step_duration', '', 1.0, 0.0), # workout_step + (27, 2): ('duration_value', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # workout_step + (27, 3): ('target_type', BaseType.ENUM, 'wkt_step_target', '', 1.0, 0.0), # workout_step + (27, 4): ('target_value', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # workout_step + (27, 5): ('custom_target_value_low', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # workout_step + (27, 6): ('custom_target_value_high', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # workout_step + (27, 7): ('intensity', BaseType.ENUM, 'intensity', '', 1.0, 0.0), # workout_step + (27, 8): ('notes', BaseType.STRING, 'string', '', 1.0, 0.0), # workout_step + (27, 9): ('equipment', BaseType.ENUM, 'workout_equipment', '', 1.0, 0.0), # workout_step + (27, 10): ('exercise_category', BaseType.UINT16, 'exercise_category', '', 1.0, 0.0), # workout_step + (27, 11): ('exercise_name', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # workout_step + (27, 12): ('exercise_weight', BaseType.UINT16, 'uint16', 'kg', 100.0, 0.0), # workout_step + (27, 13): ('weight_display_unit', BaseType.UINT16, 'fit_base_unit', '', 1.0, 0.0), # workout_step + (27, 19): ('secondary_target_type', BaseType.ENUM, 'wkt_step_target', '', 1.0, 0.0), # workout_step + (27, 20): ('secondary_target_value', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # workout_step + (27, 21): ('secondary_custom_target_value_low', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # workout_step + (27, 22): ('secondary_custom_target_value_high', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # workout_step + (27, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # workout_step + (28, 0): ('manufacturer', BaseType.UINT16, 'manufacturer', '', 1.0, 0.0), # schedule + (28, 1): ('product', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # schedule + (28, 2): ('serial_number', BaseType.UINT32Z, 'uint32z', '', 1.0, 0.0), # schedule + (28, 3): ('time_created', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # schedule + (28, 4): ('completed', BaseType.UINT8, 'bool', '', 1.0, 0.0), # schedule + (28, 5): ('type', BaseType.ENUM, 'schedule', '', 1.0, 0.0), # schedule + (28, 6): ('scheduled_time', BaseType.UINT32, 'local_date_time', '', 1.0, 0.0), # schedule + (30, 0): ('weight', BaseType.UINT16, 'weight', 'kg', 100.0, 0.0), # weight_scale + (30, 1): ('percent_fat', BaseType.UINT16, 'uint16', '%', 100.0, 0.0), # weight_scale + (30, 2): ('percent_hydration', BaseType.UINT16, 'uint16', '%', 100.0, 0.0), # weight_scale + (30, 3): ('visceral_fat_mass', BaseType.UINT16, 'uint16', 'kg', 100.0, 0.0), # weight_scale + (30, 4): ('bone_mass', BaseType.UINT16, 'uint16', 'kg', 100.0, 0.0), # weight_scale + (30, 5): ('muscle_mass', BaseType.UINT16, 'uint16', 'kg', 100.0, 0.0), # weight_scale + (30, 7): ('basal_met', BaseType.UINT16, 'uint16', 'kcal/day', 4.0, 0.0), # weight_scale + (30, 8): ('physique_rating', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # weight_scale + (30, 9): ('active_met', BaseType.UINT16, 'uint16', 'kcal/day', 4.0, 0.0), # weight_scale + (30, 10): ('metabolic_age', BaseType.UINT8, 'uint8', 'years', 1.0, 0.0), # weight_scale + (30, 11): ('visceral_fat_rating', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # weight_scale + (30, 12): ('user_profile_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # weight_scale + (30, 13): ('bmi', BaseType.UINT16, 'uint16', 'kg/m^2', 10.0, 0.0), # weight_scale + (30, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # weight_scale + (31, 4): ('sport', BaseType.ENUM, 'sport', '', 1.0, 0.0), # course + (31, 5): ('name', BaseType.STRING, 'string', '', 1.0, 0.0), # course + (31, 6): ('capabilities', BaseType.UINT32Z, 'course_capabilities', '', 1.0, 0.0), # course + (31, 7): ('sub_sport', BaseType.ENUM, 'sub_sport', '', 1.0, 0.0), # course + (32, 1): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # course_point + (32, 2): ('position_lat', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # course_point + (32, 3): ('position_long', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # course_point + (32, 4): ('distance', BaseType.UINT32, 'uint32', 'm', 100.0, 0.0), # course_point + (32, 5): ('type', BaseType.ENUM, 'course_point', '', 1.0, 0.0), # course_point + (32, 6): ('name', BaseType.STRING, 'string', '', 1.0, 0.0), # course_point + (32, 8): ('favorite', BaseType.UINT8, 'bool', '', 1.0, 0.0), # course_point + (32, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # course_point + (33, 0): ('timer_time', BaseType.UINT32, 'uint32', 's', 1.0, 0.0), # totals + (33, 1): ('distance', BaseType.UINT32, 'uint32', 'm', 1.0, 0.0), # totals + (33, 2): ('calories', BaseType.UINT32, 'uint32', 'kcal', 1.0, 0.0), # totals + (33, 3): ('sport', BaseType.ENUM, 'sport', '', 1.0, 0.0), # totals + (33, 4): ('elapsed_time', BaseType.UINT32, 'uint32', 's', 1.0, 0.0), # totals + (33, 5): ('sessions', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # totals + (33, 6): ('active_time', BaseType.UINT32, 'uint32', 's', 1.0, 0.0), # totals + (33, 9): ('sport_index', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # totals + (33, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # totals + (33, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # totals + (34, 0): ('total_timer_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # activity + (34, 1): ('num_sessions', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # activity + (34, 2): ('type', BaseType.ENUM, 'activity', '', 1.0, 0.0), # activity + (34, 3): ('event', BaseType.ENUM, 'event', '', 1.0, 0.0), # activity + (34, 4): ('event_type', BaseType.ENUM, 'event_type', '', 1.0, 0.0), # activity + (34, 5): ('local_timestamp', BaseType.UINT32, 'local_date_time', '', 1.0, 0.0), # activity + (34, 6): ('event_group', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # activity + (34, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # activity + (35, 3): ('version', BaseType.UINT16, 'uint16', '', 100.0, 0.0), # software + (35, 5): ('part_number', BaseType.STRING, 'string', '', 1.0, 0.0), # software + (35, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # software + (37, 0): ('type', BaseType.ENUM, 'file', '', 1.0, 0.0), # file_capabilities + (37, 1): ('flags', BaseType.UINT8Z, 'file_flags', '', 1.0, 0.0), # file_capabilities + (37, 2): ('directory', BaseType.STRING, 'string', '', 1.0, 0.0), # file_capabilities + (37, 3): ('max_count', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # file_capabilities + (37, 4): ('max_size', BaseType.UINT32, 'uint32', 'bytes', 1.0, 0.0), # file_capabilities + (37, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # file_capabilities + (38, 0): ('file', BaseType.ENUM, 'file', '', 1.0, 0.0), # mesg_capabilities + (38, 1): ('mesg_num', BaseType.UINT16, 'mesg_num', '', 1.0, 0.0), # mesg_capabilities + (38, 2): ('count_type', BaseType.ENUM, 'mesg_count', '', 1.0, 0.0), # mesg_capabilities + (38, 3): ('count', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # mesg_capabilities + (38, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # mesg_capabilities + (39, 0): ('file', BaseType.ENUM, 'file', '', 1.0, 0.0), # field_capabilities + (39, 1): ('mesg_num', BaseType.UINT16, 'mesg_num', '', 1.0, 0.0), # field_capabilities + (39, 2): ('field_num', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # field_capabilities + (39, 3): ('count', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # field_capabilities + (39, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # field_capabilities + (49, 0): ('software_version', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # file_creator + (49, 1): ('hardware_version', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # file_creator + (51, 0): ('systolic_pressure', BaseType.UINT16, 'uint16', 'mmHg', 1.0, 0.0), # blood_pressure + (51, 1): ('diastolic_pressure', BaseType.UINT16, 'uint16', 'mmHg', 1.0, 0.0), # blood_pressure + (51, 2): ('mean_arterial_pressure', BaseType.UINT16, 'uint16', 'mmHg', 1.0, 0.0), # blood_pressure + (51, 3): ('map_3_sample_mean', BaseType.UINT16, 'uint16', 'mmHg', 1.0, 0.0), # blood_pressure + (51, 4): ('map_morning_values', BaseType.UINT16, 'uint16', 'mmHg', 1.0, 0.0), # blood_pressure + (51, 5): ('map_evening_values', BaseType.UINT16, 'uint16', 'mmHg', 1.0, 0.0), # blood_pressure + (51, 6): ('heart_rate', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # blood_pressure + (51, 7): ('heart_rate_type', BaseType.ENUM, 'hr_type', '', 1.0, 0.0), # blood_pressure + (51, 8): ('status', BaseType.ENUM, 'bp_status', '', 1.0, 0.0), # blood_pressure + (51, 9): ('user_profile_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # blood_pressure + (51, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # blood_pressure + (53, 0): ('high_value', BaseType.UINT16, 'uint16', 'm/s', 1000.0, 0.0), # speed_zone + (53, 1): ('name', BaseType.STRING, 'string', '', 1.0, 0.0), # speed_zone + (53, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # speed_zone + (55, 0): ('device_index', BaseType.UINT8, 'device_index', '', 1.0, 0.0), # monitoring + (55, 1): ('calories', BaseType.UINT16, 'uint16', 'kcal', 1.0, 0.0), # monitoring + (55, 2): ('distance', BaseType.UINT32, 'uint32', 'm', 100.0, 0.0), # monitoring + (55, 3): ('cycles', BaseType.UINT32, 'uint32', 'cycles', 2.0, 0.0), # monitoring + (55, 4): ('active_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # monitoring + (55, 5): ('activity_type', BaseType.ENUM, 'activity_type', '', 1.0, 0.0), # monitoring + (55, 6): ('activity_subtype', BaseType.ENUM, 'activity_subtype', '', 1.0, 0.0), # monitoring + (55, 7): ('activity_level', BaseType.ENUM, 'activity_level', '', 1.0, 0.0), # monitoring + (55, 8): ('distance_16', BaseType.UINT16, 'uint16', '100 * m', 1.0, 0.0), # monitoring + (55, 9): ('cycles_16', BaseType.UINT16, 'uint16', '2 * cycles (steps)', 1.0, 0.0), # monitoring + (55, 10): ('active_time_16', BaseType.UINT16, 'uint16', 's', 1.0, 0.0), # monitoring + (55, 11): ('local_timestamp', BaseType.UINT32, 'local_date_time', '', 1.0, 0.0), # monitoring + (55, 12): ('temperature', BaseType.SINT16, 'sint16', 'C', 100.0, 0.0), # monitoring + (55, 14): ('temperature_min', BaseType.SINT16, 'sint16', 'C', 100.0, 0.0), # monitoring + (55, 15): ('temperature_max', BaseType.SINT16, 'sint16', 'C', 100.0, 0.0), # monitoring + (55, 16): ('activity_time', BaseType.UINT16, 'uint16', 'minutes', 1.0, 0.0), # monitoring + (55, 19): ('active_calories', BaseType.UINT16, 'uint16', 'kcal', 1.0, 0.0), # monitoring + (55, 24): ('current_activity_type_intensity', BaseType.BYTE, 'byte', '', 1.0, 0.0), # monitoring + (55, 25): ('timestamp_min_8', BaseType.UINT8, 'uint8', 'min', 1.0, 0.0), # monitoring + (55, 26): ('timestamp_16', BaseType.UINT16, 'uint16', 's', 1.0, 0.0), # monitoring + (55, 27): ('heart_rate', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # monitoring + (55, 28): ('intensity', BaseType.UINT8, 'uint8', '', 10.0, 0.0), # monitoring + (55, 29): ('duration_min', BaseType.UINT16, 'uint16', 'min', 1.0, 0.0), # monitoring + (55, 30): ('duration', BaseType.UINT32, 'uint32', 's', 1.0, 0.0), # monitoring + (55, 31): ('ascent', BaseType.UINT32, 'uint32', 'm', 1000.0, 0.0), # monitoring + (55, 32): ('descent', BaseType.UINT32, 'uint32', 'm', 1000.0, 0.0), # monitoring + (55, 33): ('moderate_activity_minutes', BaseType.UINT16, 'uint16', 'minutes', 1.0, 0.0), # monitoring + (55, 34): ('vigorous_activity_minutes', BaseType.UINT16, 'uint16', 'minutes', 1.0, 0.0), # monitoring + (55, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # monitoring + (72, 0): ('type', BaseType.ENUM, 'file', '', 1.0, 0.0), # training_file + (72, 1): ('manufacturer', BaseType.UINT16, 'manufacturer', '', 1.0, 0.0), # training_file + (72, 2): ('product', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # training_file + (72, 3): ('serial_number', BaseType.UINT32Z, 'uint32z', '', 1.0, 0.0), # training_file + (72, 4): ('time_created', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # training_file + (72, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # training_file + (78, 0): ('time', BaseType.UINT16, 'uint16', 's', 1000.0, 0.0), # hrv + (80, 0): ('fractional_timestamp', BaseType.UINT16, 'uint16', 's', 32768.0, 0.0), # ant_rx + (80, 1): ('mesg_id', BaseType.BYTE, 'byte', '', 1.0, 0.0), # ant_rx + (80, 2): ('mesg_data', BaseType.BYTE, 'byte', '', 1.0, 0.0), # ant_rx + (80, 3): ('channel_number', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # ant_rx + (80, 4): ('data', BaseType.BYTE, 'byte', '', 1.0, 0.0), # ant_rx + (80, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # ant_rx + (81, 0): ('fractional_timestamp', BaseType.UINT16, 'uint16', 's', 32768.0, 0.0), # ant_tx + (81, 1): ('mesg_id', BaseType.BYTE, 'byte', '', 1.0, 0.0), # ant_tx + (81, 2): ('mesg_data', BaseType.BYTE, 'byte', '', 1.0, 0.0), # ant_tx + (81, 3): ('channel_number', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # ant_tx + (81, 4): ('data', BaseType.BYTE, 'byte', '', 1.0, 0.0), # ant_tx + (81, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # ant_tx + (82, 0): ('channel_number', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # ant_channel_id + (82, 1): ('device_type', BaseType.UINT8Z, 'uint8z', '', 1.0, 0.0), # ant_channel_id + (82, 2): ('device_number', BaseType.UINT16Z, 'uint16z', '', 1.0, 0.0), # ant_channel_id + (82, 3): ('transmission_type', BaseType.UINT8Z, 'uint8z', '', 1.0, 0.0), # ant_channel_id + (82, 4): ('device_index', BaseType.UINT8, 'device_index', '', 1.0, 0.0), # ant_channel_id + (101, 0): ('event', BaseType.ENUM, 'event', '', 1.0, 0.0), # length + (101, 1): ('event_type', BaseType.ENUM, 'event_type', '', 1.0, 0.0), # length + (101, 2): ('start_time', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # length + (101, 3): ('total_elapsed_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # length + (101, 4): ('total_timer_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # length + (101, 5): ('total_strokes', BaseType.UINT16, 'uint16', 'strokes', 1.0, 0.0), # length + (101, 6): ('avg_speed', BaseType.UINT16, 'uint16', 'm/s', 1000.0, 0.0), # length + (101, 7): ('swim_stroke', BaseType.ENUM, 'swim_stroke', 'swim_stroke', 1.0, 0.0), # length + (101, 9): ('avg_swimming_cadence', BaseType.UINT8, 'uint8', 'strokes/min', 1.0, 0.0), # length + (101, 10): ('event_group', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # length + (101, 11): ('total_calories', BaseType.UINT16, 'uint16', 'kcal', 1.0, 0.0), # length + (101, 12): ('length_type', BaseType.ENUM, 'length_type', '', 1.0, 0.0), # length + (101, 18): ('player_score', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # length + (101, 19): ('opponent_score', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # length + (101, 20): ('stroke_count', BaseType.UINT16, 'uint16', 'counts', 1.0, 0.0), # length + (101, 21): ('zone_count', BaseType.UINT16, 'uint16', 'counts', 1.0, 0.0), # length + (101, 22): ('enhanced_avg_respiration_rate', BaseType.UINT16, 'uint16', 'Breaths/min', 100.0, 0.0), # length + (101, 23): ('enhanced_max_respiration_rate', BaseType.UINT16, 'uint16', 'Breaths/min', 100.0, 0.0), # length + (101, 24): ('avg_respiration_rate', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # length + (101, 25): ('max_respiration_rate', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # length + (101, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # length + (101, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # length + (103, 0): ('local_timestamp', BaseType.UINT32, 'local_date_time', 's', 1.0, 0.0), # monitoring_info + (103, 1): ('activity_type', BaseType.ENUM, 'activity_type', '', 1.0, 0.0), # monitoring_info + (103, 3): ('cycles_to_distance', BaseType.UINT16, 'uint16', 'm/cycle', 5000.0, 0.0), # monitoring_info + (103, 4): ('cycles_to_calories', BaseType.UINT16, 'uint16', 'kcal/cycle', 5000.0, 0.0), # monitoring_info + (103, 5): ('resting_metabolic_rate', BaseType.UINT16, 'uint16', 'kcal / day', 1.0, 0.0), # monitoring_info + (103, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # monitoring_info + (106, 0): ('manufacturer', BaseType.UINT16, 'manufacturer', '', 1.0, 0.0), # slave_device + (106, 1): ('product', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # slave_device + (127, 0): ('bluetooth_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # connectivity + (127, 1): ('bluetooth_le_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # connectivity + (127, 2): ('ant_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # connectivity + (127, 3): ('name', BaseType.STRING, 'string', '', 1.0, 0.0), # connectivity + (127, 4): ('live_tracking_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # connectivity + (127, 5): ('weather_conditions_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # connectivity + (127, 6): ('weather_alerts_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # connectivity + (127, 7): ('auto_activity_upload_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # connectivity + (127, 8): ('course_download_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # connectivity + (127, 9): ('workout_download_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # connectivity + (127, 10): ('gps_ephemeris_download_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # connectivity + (127, 11): ('incident_detection_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # connectivity + (127, 12): ('grouptrack_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # connectivity + (128, 0): ('weather_report', BaseType.ENUM, 'weather_report', '', 1.0, 0.0), # weather_conditions + (128, 1): ('temperature', BaseType.SINT8, 'sint8', 'C', 1.0, 0.0), # weather_conditions + (128, 2): ('condition', BaseType.ENUM, 'weather_status', '', 1.0, 0.0), # weather_conditions + (128, 3): ('wind_direction', BaseType.UINT16, 'uint16', 'degrees', 1.0, 0.0), # weather_conditions + (128, 4): ('wind_speed', BaseType.UINT16, 'uint16', 'm/s', 1000.0, 0.0), # weather_conditions + (128, 5): ('precipitation_probability', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # weather_conditions + (128, 6): ('temperature_feels_like', BaseType.SINT8, 'sint8', 'C', 1.0, 0.0), # weather_conditions + (128, 7): ('relative_humidity', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # weather_conditions + (128, 8): ('location', BaseType.STRING, 'string', '', 1.0, 0.0), # weather_conditions + (128, 9): ('observed_at_time', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # weather_conditions + (128, 10): ('observed_location_lat', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # weather_conditions + (128, 11): ('observed_location_long', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # weather_conditions + (128, 12): ('day_of_week', BaseType.ENUM, 'day_of_week', '', 1.0, 0.0), # weather_conditions + (128, 13): ('high_temperature', BaseType.SINT8, 'sint8', 'C', 1.0, 0.0), # weather_conditions + (128, 14): ('low_temperature', BaseType.SINT8, 'sint8', 'C', 1.0, 0.0), # weather_conditions + (128, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # weather_conditions + (129, 0): ('report_id', BaseType.STRING, 'string', '', 1.0, 0.0), # weather_alert + (129, 1): ('issue_time', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # weather_alert + (129, 2): ('expire_time', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # weather_alert + (129, 3): ('severity', BaseType.ENUM, 'weather_severity', '', 1.0, 0.0), # weather_alert + (129, 4): ('type', BaseType.ENUM, 'weather_severe_type', '', 1.0, 0.0), # weather_alert + (129, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # weather_alert + (131, 0): ('high_value', BaseType.UINT8, 'uint8', 'rpm', 1.0, 0.0), # cadence_zone + (131, 1): ('name', BaseType.STRING, 'string', '', 1.0, 0.0), # cadence_zone + (131, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # cadence_zone + (132, 0): ('fractional_timestamp', BaseType.UINT16, 'uint16', 's', 32768.0, 0.0), # hr + (132, 1): ('time256', BaseType.UINT8, 'uint8', 's', 256.0, 0.0), # hr + (132, 6): ('filtered_bpm', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # hr + (132, 9): ('event_timestamp', BaseType.UINT32, 'uint32', 's', 1024.0, 0.0), # hr + (132, 10): ('event_timestamp_12', BaseType.BYTE, 'byte', 's', 1.0, 0.0), # hr + (132, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # hr + (142, 0): ('event', BaseType.ENUM, 'event', '', 1.0, 0.0), # segment_lap + (142, 1): ('event_type', BaseType.ENUM, 'event_type', '', 1.0, 0.0), # segment_lap + (142, 2): ('start_time', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # segment_lap + (142, 3): ('start_position_lat', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # segment_lap + (142, 4): ('start_position_long', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # segment_lap + (142, 5): ('end_position_lat', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # segment_lap + (142, 6): ('end_position_long', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # segment_lap + (142, 7): ('total_elapsed_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # segment_lap + (142, 8): ('total_timer_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # segment_lap + (142, 9): ('total_distance', BaseType.UINT32, 'uint32', 'm', 100.0, 0.0), # segment_lap + (142, 10): ('total_cycles', BaseType.UINT32, 'uint32', 'cycles', 1.0, 0.0), # segment_lap + (142, 11): ('total_calories', BaseType.UINT16, 'uint16', 'kcal', 1.0, 0.0), # segment_lap + (142, 12): ('total_fat_calories', BaseType.UINT16, 'uint16', 'kcal', 1.0, 0.0), # segment_lap + (142, 13): ('avg_speed', BaseType.UINT16, 'uint16', 'm/s', 1000.0, 0.0), # segment_lap + (142, 14): ('max_speed', BaseType.UINT16, 'uint16', 'm/s', 1000.0, 0.0), # segment_lap + (142, 15): ('avg_heart_rate', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # segment_lap + (142, 16): ('max_heart_rate', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # segment_lap + (142, 17): ('avg_cadence', BaseType.UINT8, 'uint8', 'rpm', 1.0, 0.0), # segment_lap + (142, 18): ('max_cadence', BaseType.UINT8, 'uint8', 'rpm', 1.0, 0.0), # segment_lap + (142, 19): ('avg_power', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # segment_lap + (142, 20): ('max_power', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # segment_lap + (142, 21): ('total_ascent', BaseType.UINT16, 'uint16', 'm', 1.0, 0.0), # segment_lap + (142, 22): ('total_descent', BaseType.UINT16, 'uint16', 'm', 1.0, 0.0), # segment_lap + (142, 23): ('sport', BaseType.ENUM, 'sport', '', 1.0, 0.0), # segment_lap + (142, 24): ('event_group', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # segment_lap + (142, 25): ('nec_lat', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # segment_lap + (142, 26): ('nec_long', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # segment_lap + (142, 27): ('swc_lat', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # segment_lap + (142, 28): ('swc_long', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # segment_lap + (142, 29): ('name', BaseType.STRING, 'string', '', 1.0, 0.0), # segment_lap + (142, 30): ('normalized_power', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # segment_lap + (142, 31): ('left_right_balance', BaseType.UINT16, 'left_right_balance_100', '', 1.0, 0.0), # segment_lap + (142, 32): ('sub_sport', BaseType.ENUM, 'sub_sport', '', 1.0, 0.0), # segment_lap + (142, 33): ('total_work', BaseType.UINT32, 'uint32', 'J', 1.0, 0.0), # segment_lap + (142, 34): ('avg_altitude', BaseType.UINT16, 'uint16', 'm', 5.0, 500.0), # segment_lap + (142, 35): ('max_altitude', BaseType.UINT16, 'uint16', 'm', 5.0, 500.0), # segment_lap + (142, 36): ('gps_accuracy', BaseType.UINT8, 'uint8', 'm', 1.0, 0.0), # segment_lap + (142, 37): ('avg_grade', BaseType.SINT16, 'sint16', '%', 100.0, 0.0), # segment_lap + (142, 38): ('avg_pos_grade', BaseType.SINT16, 'sint16', '%', 100.0, 0.0), # segment_lap + (142, 39): ('avg_neg_grade', BaseType.SINT16, 'sint16', '%', 100.0, 0.0), # segment_lap + (142, 40): ('max_pos_grade', BaseType.SINT16, 'sint16', '%', 100.0, 0.0), # segment_lap + (142, 41): ('max_neg_grade', BaseType.SINT16, 'sint16', '%', 100.0, 0.0), # segment_lap + (142, 42): ('avg_temperature', BaseType.SINT8, 'sint8', 'C', 1.0, 0.0), # segment_lap + (142, 43): ('max_temperature', BaseType.SINT8, 'sint8', 'C', 1.0, 0.0), # segment_lap + (142, 44): ('total_moving_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # segment_lap + (142, 45): ('avg_pos_vertical_speed', BaseType.SINT16, 'sint16', 'm/s', 1000.0, 0.0), # segment_lap + (142, 46): ('avg_neg_vertical_speed', BaseType.SINT16, 'sint16', 'm/s', 1000.0, 0.0), # segment_lap + (142, 47): ('max_pos_vertical_speed', BaseType.SINT16, 'sint16', 'm/s', 1000.0, 0.0), # segment_lap + (142, 48): ('max_neg_vertical_speed', BaseType.SINT16, 'sint16', 'm/s', 1000.0, 0.0), # segment_lap + (142, 49): ('time_in_hr_zone', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # segment_lap + (142, 50): ('time_in_speed_zone', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # segment_lap + (142, 51): ('time_in_cadence_zone', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # segment_lap + (142, 52): ('time_in_power_zone', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # segment_lap + (142, 53): ('repetition_num', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # segment_lap + (142, 54): ('min_altitude', BaseType.UINT16, 'uint16', 'm', 5.0, 500.0), # segment_lap + (142, 55): ('min_heart_rate', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # segment_lap + (142, 56): ('active_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # segment_lap + (142, 57): ('wkt_step_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # segment_lap + (142, 58): ('sport_event', BaseType.ENUM, 'sport_event', '', 1.0, 0.0), # segment_lap + (142, 59): ('avg_left_torque_effectiveness', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # segment_lap + (142, 60): ('avg_right_torque_effectiveness', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # segment_lap + (142, 61): ('avg_left_pedal_smoothness', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # segment_lap + (142, 62): ('avg_right_pedal_smoothness', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # segment_lap + (142, 63): ('avg_combined_pedal_smoothness', BaseType.UINT8, 'uint8', 'percent', 2.0, 0.0), # segment_lap + (142, 64): ('status', BaseType.ENUM, 'segment_lap_status', '', 1.0, 0.0), # segment_lap + (142, 65): ('uuid', BaseType.STRING, 'string', '', 1.0, 0.0), # segment_lap + (142, 66): ('avg_fractional_cadence', BaseType.UINT8, 'uint8', 'rpm', 128.0, 0.0), # segment_lap + (142, 67): ('max_fractional_cadence', BaseType.UINT8, 'uint8', 'rpm', 128.0, 0.0), # segment_lap + (142, 68): ('total_fractional_cycles', BaseType.UINT8, 'uint8', 'cycles', 128.0, 0.0), # segment_lap + (142, 69): ('front_gear_shift_count', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # segment_lap + (142, 70): ('rear_gear_shift_count', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # segment_lap + (142, 71): ('time_standing', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # segment_lap + (142, 72): ('stand_count', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # segment_lap + (142, 73): ('avg_left_pco', BaseType.SINT8, 'sint8', 'mm', 1.0, 0.0), # segment_lap + (142, 74): ('avg_right_pco', BaseType.SINT8, 'sint8', 'mm', 1.0, 0.0), # segment_lap + (142, 75): ('avg_left_power_phase', BaseType.UINT8, 'uint8', 'degrees', 0.7111111, 0.0), # segment_lap + (142, 76): ('avg_left_power_phase_peak', BaseType.UINT8, 'uint8', 'degrees', 0.7111111, 0.0), # segment_lap + (142, 77): ('avg_right_power_phase', BaseType.UINT8, 'uint8', 'degrees', 0.7111111, 0.0), # segment_lap + (142, 78): ('avg_right_power_phase_peak', BaseType.UINT8, 'uint8', 'degrees', 0.7111111, 0.0), # segment_lap + (142, 79): ('avg_power_position', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # segment_lap + (142, 80): ('max_power_position', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # segment_lap + (142, 81): ('avg_cadence_position', BaseType.UINT8, 'uint8', 'rpm', 1.0, 0.0), # segment_lap + (142, 82): ('max_cadence_position', BaseType.UINT8, 'uint8', 'rpm', 1.0, 0.0), # segment_lap + (142, 83): ('manufacturer', BaseType.UINT16, 'manufacturer', '', 1.0, 0.0), # segment_lap + (142, 84): ('total_grit', BaseType.FLOAT32, 'float32', 'kGrit', 1.0, 0.0), # segment_lap + (142, 85): ('total_flow', BaseType.FLOAT32, 'float32', 'Flow', 1.0, 0.0), # segment_lap + (142, 86): ('avg_grit', BaseType.FLOAT32, 'float32', 'kGrit', 1.0, 0.0), # segment_lap + (142, 87): ('avg_flow', BaseType.FLOAT32, 'float32', 'Flow', 1.0, 0.0), # segment_lap + (142, 89): ('total_fractional_ascent', BaseType.UINT8, 'uint8', 'm', 100.0, 0.0), # segment_lap + (142, 90): ('total_fractional_descent', BaseType.UINT8, 'uint8', 'm', 100.0, 0.0), # segment_lap + (142, 91): ('enhanced_avg_altitude', BaseType.UINT32, 'uint32', 'm', 5.0, 500.0), # segment_lap + (142, 92): ('enhanced_max_altitude', BaseType.UINT32, 'uint32', 'm', 5.0, 500.0), # segment_lap + (142, 93): ('enhanced_min_altitude', BaseType.UINT32, 'uint32', 'm', 5.0, 500.0), # segment_lap + (142, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # segment_lap + (142, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # segment_lap + (145, 0): ('memo', BaseType.BYTE, 'byte', '', 1.0, 0.0), # memo_glob + (145, 1): ('mesg_num', BaseType.UINT16, 'mesg_num', '', 1.0, 0.0), # memo_glob + (145, 2): ('parent_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # memo_glob + (145, 3): ('field_num', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # memo_glob + (145, 4): ('data', BaseType.UINT8Z, 'uint8z', '', 1.0, 0.0), # memo_glob + (145, 250): ('part_index', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # memo_glob + (148, 0): ('name', BaseType.STRING, 'string', '', 1.0, 0.0), # segment_id + (148, 1): ('uuid', BaseType.STRING, 'string', '', 1.0, 0.0), # segment_id + (148, 2): ('sport', BaseType.ENUM, 'sport', '', 1.0, 0.0), # segment_id + (148, 3): ('enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # segment_id + (148, 4): ('user_profile_primary_key', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # segment_id + (148, 5): ('device_id', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # segment_id + (148, 6): ('default_race_leader', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # segment_id + (148, 7): ('delete_status', BaseType.ENUM, 'segment_delete_status', '', 1.0, 0.0), # segment_id + (148, 8): ('selection_type', BaseType.ENUM, 'segment_selection_type', '', 1.0, 0.0), # segment_id + (149, 0): ('name', BaseType.STRING, 'string', '', 1.0, 0.0), # segment_leaderboard_entry + (149, 1): ('type', BaseType.ENUM, 'segment_leaderboard_type', '', 1.0, 0.0), # segment_leaderboard_entry + (149, 2): ('group_primary_key', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # segment_leaderboard_entry + (149, 3): ('activity_id', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # segment_leaderboard_entry + (149, 4): ('segment_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # segment_leaderboard_entry + (149, 5): ('activity_id_string', BaseType.STRING, 'string', '', 1.0, 0.0), # segment_leaderboard_entry + (149, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # segment_leaderboard_entry + (150, 1): ('position_lat', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # segment_point + (150, 2): ('position_long', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # segment_point + (150, 3): ('distance', BaseType.UINT32, 'uint32', 'm', 100.0, 0.0), # segment_point + (150, 4): ('altitude', BaseType.UINT16, 'uint16', 'm', 5.0, 500.0), # segment_point + (150, 5): ('leader_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # segment_point + (150, 6): ('enhanced_altitude', BaseType.UINT32, 'uint32', 'm', 5.0, 500.0), # segment_point + (150, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # segment_point + (151, 1): ('file_uuid', BaseType.STRING, 'string', '', 1.0, 0.0), # segment_file + (151, 3): ('enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # segment_file + (151, 4): ('user_profile_primary_key', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # segment_file + (151, 7): ('leader_type', BaseType.ENUM, 'segment_leaderboard_type', '', 1.0, 0.0), # segment_file + (151, 8): ('leader_group_primary_key', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # segment_file + (151, 9): ('leader_activity_id', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # segment_file + (151, 10): ('leader_activity_id_string', BaseType.STRING, 'string', '', 1.0, 0.0), # segment_file + (151, 11): ('default_race_leader', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # segment_file + (151, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # segment_file + (158, 0): ('sport', BaseType.ENUM, 'sport', '', 1.0, 0.0), # workout_session + (158, 1): ('sub_sport', BaseType.ENUM, 'sub_sport', '', 1.0, 0.0), # workout_session + (158, 2): ('num_valid_steps', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # workout_session + (158, 3): ('first_step_index', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # workout_session + (158, 4): ('pool_length', BaseType.UINT16, 'uint16', 'm', 100.0, 0.0), # workout_session + (158, 5): ('pool_length_unit', BaseType.ENUM, 'display_measure', '', 1.0, 0.0), # workout_session + (158, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # workout_session + (159, 0): ('mode', BaseType.ENUM, 'watchface_mode', '', 1.0, 0.0), # watchface_settings + (159, 1): ('layout', BaseType.BYTE, 'byte', '', 1.0, 0.0), # watchface_settings + (159, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # watchface_settings + (160, 0): ('timestamp_ms', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # gps_metadata + (160, 1): ('position_lat', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # gps_metadata + (160, 2): ('position_long', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # gps_metadata + (160, 3): ('enhanced_altitude', BaseType.UINT32, 'uint32', 'm', 5.0, 500.0), # gps_metadata + (160, 4): ('enhanced_speed', BaseType.UINT32, 'uint32', 'm/s', 1000.0, 0.0), # gps_metadata + (160, 5): ('heading', BaseType.UINT16, 'uint16', 'degrees', 100.0, 0.0), # gps_metadata + (160, 6): ('utc_timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # gps_metadata + (160, 7): ('velocity', BaseType.SINT16, 'sint16', 'm/s', 100.0, 0.0), # gps_metadata + (160, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # gps_metadata + (161, 0): ('timestamp_ms', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # camera_event + (161, 1): ('camera_event_type', BaseType.ENUM, 'camera_event_type', '', 1.0, 0.0), # camera_event + (161, 2): ('camera_file_uuid', BaseType.STRING, 'string', '', 1.0, 0.0), # camera_event + (161, 3): ('camera_orientation', BaseType.ENUM, 'camera_orientation_type', '', 1.0, 0.0), # camera_event + (161, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # camera_event + (162, 0): ('fractional_timestamp', BaseType.UINT16, 'uint16', 's', 32768.0, 0.0), # timestamp_correlation + (162, 1): ('system_timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # timestamp_correlation + (162, 2): ('fractional_system_timestamp', BaseType.UINT16, 'uint16', 's', 32768.0, 0.0), # timestamp_correlation + (162, 3): ('local_timestamp', BaseType.UINT32, 'local_date_time', 's', 1.0, 0.0), # timestamp_correlation + (162, 4): ('timestamp_ms', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # timestamp_correlation + (162, 5): ('system_timestamp_ms', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # timestamp_correlation + (162, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # timestamp_correlation + (164, 0): ('timestamp_ms', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # gyroscope_data + (164, 1): ('sample_time_offset', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # gyroscope_data + (164, 2): ('gyro_x', BaseType.UINT16, 'uint16', 'counts', 1.0, 0.0), # gyroscope_data + (164, 3): ('gyro_y', BaseType.UINT16, 'uint16', 'counts', 1.0, 0.0), # gyroscope_data + (164, 4): ('gyro_z', BaseType.UINT16, 'uint16', 'counts', 1.0, 0.0), # gyroscope_data + (164, 5): ('calibrated_gyro_x', BaseType.FLOAT32, 'float32', 'deg/s', 1.0, 0.0), # gyroscope_data + (164, 6): ('calibrated_gyro_y', BaseType.FLOAT32, 'float32', 'deg/s', 1.0, 0.0), # gyroscope_data + (164, 7): ('calibrated_gyro_z', BaseType.FLOAT32, 'float32', 'deg/s', 1.0, 0.0), # gyroscope_data + (164, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # gyroscope_data + (165, 0): ('timestamp_ms', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # accelerometer_data + (165, 1): ('sample_time_offset', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # accelerometer_data + (165, 2): ('accel_x', BaseType.UINT16, 'uint16', 'counts', 1.0, 0.0), # accelerometer_data + (165, 3): ('accel_y', BaseType.UINT16, 'uint16', 'counts', 1.0, 0.0), # accelerometer_data + (165, 4): ('accel_z', BaseType.UINT16, 'uint16', 'counts', 1.0, 0.0), # accelerometer_data + (165, 5): ('calibrated_accel_x', BaseType.FLOAT32, 'float32', 'g', 1.0, 0.0), # accelerometer_data + (165, 6): ('calibrated_accel_y', BaseType.FLOAT32, 'float32', 'g', 1.0, 0.0), # accelerometer_data + (165, 7): ('calibrated_accel_z', BaseType.FLOAT32, 'float32', 'g', 1.0, 0.0), # accelerometer_data + (165, 8): ('compressed_calibrated_accel_x', BaseType.SINT16, 'sint16', 'mG', 1.0, 0.0), # accelerometer_data + (165, 9): ('compressed_calibrated_accel_y', BaseType.SINT16, 'sint16', 'mG', 1.0, 0.0), # accelerometer_data + (165, 10): ('compressed_calibrated_accel_z', BaseType.SINT16, 'sint16', 'mG', 1.0, 0.0), # accelerometer_data + (165, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # accelerometer_data + (167, 0): ('sensor_type', BaseType.ENUM, 'sensor_type', '', 1.0, 0.0), # three_d_sensor_calibration + (167, 1): ('calibration_factor', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # three_d_sensor_calibration + (167, 2): ('calibration_divisor', BaseType.UINT32, 'uint32', 'counts', 1.0, 0.0), # three_d_sensor_calibration + (167, 3): ('level_shift', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # three_d_sensor_calibration + (167, 4): ('offset_cal', BaseType.SINT32, 'sint32', '', 1.0, 0.0), # three_d_sensor_calibration + (167, 5): ('orientation_matrix', BaseType.SINT32, 'sint32', '', 65535.0, 0.0), # three_d_sensor_calibration + (167, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # three_d_sensor_calibration + (169, 0): ('timestamp_ms', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # video_frame + (169, 1): ('frame_number', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # video_frame + (169, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # video_frame + (174, 0): ('timestamp_ms', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # obdii_data + (174, 1): ('time_offset', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # obdii_data + (174, 2): ('pid', BaseType.BYTE, 'byte', '', 1.0, 0.0), # obdii_data + (174, 3): ('raw_data', BaseType.BYTE, 'byte', '', 1.0, 0.0), # obdii_data + (174, 4): ('pid_data_size', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # obdii_data + (174, 5): ('system_time', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # obdii_data + (174, 6): ('start_timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # obdii_data + (174, 7): ('start_timestamp_ms', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # obdii_data + (174, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # obdii_data + (177, 0): ('timestamp_ms', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # nmea_sentence + (177, 1): ('sentence', BaseType.STRING, 'string', '', 1.0, 0.0), # nmea_sentence + (177, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # nmea_sentence + (178, 0): ('timestamp_ms', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # aviation_attitude + (178, 1): ('system_time', BaseType.UINT32, 'uint32', 'ms', 1.0, 0.0), # aviation_attitude + (178, 2): ('pitch', BaseType.SINT16, 'sint16', 'radians', 10430.38, 0.0), # aviation_attitude + (178, 3): ('roll', BaseType.SINT16, 'sint16', 'radians', 10430.38, 0.0), # aviation_attitude + (178, 4): ('accel_lateral', BaseType.SINT16, 'sint16', 'm/s^2', 100.0, 0.0), # aviation_attitude + (178, 5): ('accel_normal', BaseType.SINT16, 'sint16', 'm/s^2', 100.0, 0.0), # aviation_attitude + (178, 6): ('turn_rate', BaseType.SINT16, 'sint16', 'radians/second', 1024.0, 0.0), # aviation_attitude + (178, 7): ('stage', BaseType.ENUM, 'attitude_stage', '', 1.0, 0.0), # aviation_attitude + (178, 8): ('attitude_stage_complete', BaseType.UINT8, 'uint8', '%', 1.0, 0.0), # aviation_attitude + (178, 9): ('track', BaseType.UINT16, 'uint16', 'radians', 10430.38, 0.0), # aviation_attitude + (178, 10): ('validity', BaseType.UINT16, 'attitude_validity', '', 1.0, 0.0), # aviation_attitude + (178, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # aviation_attitude + (184, 0): ('url', BaseType.STRING, 'string', '', 1.0, 0.0), # video + (184, 1): ('hosting_provider', BaseType.STRING, 'string', '', 1.0, 0.0), # video + (184, 2): ('duration', BaseType.UINT32, 'uint32', 'ms', 1.0, 0.0), # video + (185, 0): ('message_count', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # video_title + (185, 1): ('text', BaseType.STRING, 'string', '', 1.0, 0.0), # video_title + (185, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # video_title + (186, 0): ('message_count', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # video_description + (186, 1): ('text', BaseType.STRING, 'string', '', 1.0, 0.0), # video_description + (186, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # video_description + (187, 0): ('clip_number', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # video_clip + (187, 1): ('start_timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # video_clip + (187, 2): ('start_timestamp_ms', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # video_clip + (187, 3): ('end_timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # video_clip + (187, 4): ('end_timestamp_ms', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # video_clip + (187, 6): ('clip_start', BaseType.UINT32, 'uint32', 'ms', 1.0, 0.0), # video_clip + (187, 7): ('clip_end', BaseType.UINT32, 'uint32', 'ms', 1.0, 0.0), # video_clip + (188, 0): ('enabled', BaseType.ENUM, 'switch', '', 1.0, 0.0), # ohr_settings + (188, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # ohr_settings + (200, 0): ('screen_index', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # exd_screen_configuration + (200, 1): ('field_count', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # exd_screen_configuration + (200, 2): ('layout', BaseType.ENUM, 'exd_layout', '', 1.0, 0.0), # exd_screen_configuration + (200, 3): ('screen_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # exd_screen_configuration + (201, 0): ('screen_index', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # exd_data_field_configuration + (201, 1): ('concept_field', BaseType.BYTE, 'byte', '', 1.0, 0.0), # exd_data_field_configuration + (201, 2): ('field_id', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # exd_data_field_configuration + (201, 3): ('concept_count', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # exd_data_field_configuration + (201, 4): ('display_type', BaseType.ENUM, 'exd_display_type', '', 1.0, 0.0), # exd_data_field_configuration + (201, 5): ('title', BaseType.STRING, 'string', '', 1.0, 0.0), # exd_data_field_configuration + (202, 0): ('screen_index', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # exd_data_concept_configuration + (202, 1): ('concept_field', BaseType.BYTE, 'byte', '', 1.0, 0.0), # exd_data_concept_configuration + (202, 2): ('field_id', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # exd_data_concept_configuration + (202, 3): ('concept_index', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # exd_data_concept_configuration + (202, 4): ('data_page', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # exd_data_concept_configuration + (202, 5): ('concept_key', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # exd_data_concept_configuration + (202, 6): ('scaling', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # exd_data_concept_configuration + (202, 8): ('data_units', BaseType.ENUM, 'exd_data_units', '', 1.0, 0.0), # exd_data_concept_configuration + (202, 9): ('qualifier', BaseType.ENUM, 'exd_qualifiers', '', 1.0, 0.0), # exd_data_concept_configuration + (202, 10): ('descriptor', BaseType.ENUM, 'exd_descriptors', '', 1.0, 0.0), # exd_data_concept_configuration + (202, 11): ('is_signed', BaseType.UINT8, 'bool', '', 1.0, 0.0), # exd_data_concept_configuration + (206, 0): ('developer_data_index', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # field_description + (206, 1): ('field_definition_number', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # field_description + (206, 2): ('fit_base_type_id', BaseType.UINT8, 'fit_base_type', '', 1.0, 0.0), # field_description + (206, 3): ('field_name', BaseType.STRING, 'string', '', 1.0, 0.0), # field_description + (206, 4): ('array', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # field_description + (206, 5): ('components', BaseType.STRING, 'string', '', 1.0, 0.0), # field_description + (206, 6): ('scale', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # field_description + (206, 7): ('offset', BaseType.SINT8, 'sint8', '', 1.0, 0.0), # field_description + (206, 8): ('units', BaseType.STRING, 'string', '', 1.0, 0.0), # field_description + (206, 9): ('bits', BaseType.STRING, 'string', '', 1.0, 0.0), # field_description + (206, 10): ('accumulate', BaseType.STRING, 'string', '', 1.0, 0.0), # field_description + (206, 13): ('fit_base_unit_id', BaseType.UINT16, 'fit_base_unit', '', 1.0, 0.0), # field_description + (206, 14): ('native_mesg_num', BaseType.UINT16, 'mesg_num', '', 1.0, 0.0), # field_description + (206, 15): ('native_field_num', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # field_description + (207, 0): ('developer_id', BaseType.BYTE, 'byte', '', 1.0, 0.0), # developer_data_id + (207, 1): ('application_id', BaseType.BYTE, 'byte', '', 1.0, 0.0), # developer_data_id + (207, 2): ('manufacturer_id', BaseType.UINT16, 'manufacturer', '', 1.0, 0.0), # developer_data_id + (207, 3): ('developer_data_index', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # developer_data_id + (207, 4): ('application_version', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # developer_data_id + (208, 0): ('timestamp_ms', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # magnetometer_data + (208, 1): ('sample_time_offset', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # magnetometer_data + (208, 2): ('mag_x', BaseType.UINT16, 'uint16', 'counts', 1.0, 0.0), # magnetometer_data + (208, 3): ('mag_y', BaseType.UINT16, 'uint16', 'counts', 1.0, 0.0), # magnetometer_data + (208, 4): ('mag_z', BaseType.UINT16, 'uint16', 'counts', 1.0, 0.0), # magnetometer_data + (208, 5): ('calibrated_mag_x', BaseType.FLOAT32, 'float32', 'G', 1.0, 0.0), # magnetometer_data + (208, 6): ('calibrated_mag_y', BaseType.FLOAT32, 'float32', 'G', 1.0, 0.0), # magnetometer_data + (208, 7): ('calibrated_mag_z', BaseType.FLOAT32, 'float32', 'G', 1.0, 0.0), # magnetometer_data + (208, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # magnetometer_data + (209, 0): ('timestamp_ms', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # barometer_data + (209, 1): ('sample_time_offset', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # barometer_data + (209, 2): ('baro_pres', BaseType.UINT32, 'uint32', 'Pa', 1.0, 0.0), # barometer_data + (209, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # barometer_data + (210, 0): ('sensor_type', BaseType.ENUM, 'sensor_type', '', 1.0, 0.0), # one_d_sensor_calibration + (210, 1): ('calibration_factor', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # one_d_sensor_calibration + (210, 2): ('calibration_divisor', BaseType.UINT32, 'uint32', 'counts', 1.0, 0.0), # one_d_sensor_calibration + (210, 3): ('level_shift', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # one_d_sensor_calibration + (210, 4): ('offset_cal', BaseType.SINT32, 'sint32', '', 1.0, 0.0), # one_d_sensor_calibration + (210, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # one_d_sensor_calibration + (211, 0): ('resting_heart_rate', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # monitoring_hr_data + (211, 1): ('current_day_resting_heart_rate', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # monitoring_hr_data + (211, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # monitoring_hr_data + (216, 0): ('reference_mesg', BaseType.UINT16, 'mesg_num', '', 1.0, 0.0), # time_in_zone + (216, 1): ('reference_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # time_in_zone + (216, 2): ('time_in_hr_zone', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # time_in_zone + (216, 3): ('time_in_speed_zone', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # time_in_zone + (216, 4): ('time_in_cadence_zone', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # time_in_zone + (216, 5): ('time_in_power_zone', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # time_in_zone + (216, 6): ('hr_zone_high_boundary', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # time_in_zone + (216, 7): ('speed_zone_high_boundary', BaseType.UINT16, 'uint16', 'm/s', 1000.0, 0.0), # time_in_zone + (216, 8): ('cadence_zone_high_bondary', BaseType.UINT8, 'uint8', 'rpm', 1.0, 0.0), # time_in_zone + (216, 9): ('power_zone_high_boundary', BaseType.UINT16, 'uint16', 'watts', 1.0, 0.0), # time_in_zone + (216, 10): ('hr_calc_type', BaseType.ENUM, 'hr_zone_calc', '', 1.0, 0.0), # time_in_zone + (216, 11): ('max_heart_rate', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # time_in_zone + (216, 12): ('resting_heart_rate', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # time_in_zone + (216, 13): ('threshold_heart_rate', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # time_in_zone + (216, 14): ('pwr_calc_type', BaseType.ENUM, 'pwr_zone_calc', '', 1.0, 0.0), # time_in_zone + (216, 15): ('functional_threshold_power', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # time_in_zone + (216, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # time_in_zone + (225, 0): ('duration', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # set + (225, 3): ('repetitions', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # set + (225, 4): ('weight', BaseType.UINT16, 'uint16', 'kg', 16.0, 0.0), # set + (225, 5): ('set_type', BaseType.UINT8, 'set_type', '', 1.0, 0.0), # set + (225, 6): ('start_time', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # set + (225, 7): ('category', BaseType.UINT16, 'exercise_category', '', 1.0, 0.0), # set + (225, 8): ('category_subtype', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # set + (225, 9): ('weight_display_unit', BaseType.UINT16, 'fit_base_unit', '', 1.0, 0.0), # set + (225, 10): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # set + (225, 11): ('wkt_step_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # set + (225, 254): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # set + (227, 0): ('stress_level_value', BaseType.SINT16, 'sint16', '', 1.0, 0.0), # stress_level + (227, 1): ('stress_level_time', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # stress_level + (229, 0): ('update_time', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # max_met_data + (229, 2): ('vo2_max', BaseType.UINT16, 'uint16', 'mL/kg/min', 10.0, 0.0), # max_met_data + (229, 5): ('sport', BaseType.ENUM, 'sport', '', 1.0, 0.0), # max_met_data + (229, 6): ('sub_sport', BaseType.ENUM, 'sub_sport', '', 1.0, 0.0), # max_met_data + (229, 8): ('max_met_category', BaseType.ENUM, 'max_met_category', '', 1.0, 0.0), # max_met_data + (229, 9): ('calibrated_data', BaseType.UINT8, 'bool', '', 1.0, 0.0), # max_met_data + (229, 12): ('hr_source', BaseType.ENUM, 'max_met_heart_rate_source', '', 1.0, 0.0), # max_met_data + (229, 13): ('speed_source', BaseType.ENUM, 'max_met_speed_source', '', 1.0, 0.0), # max_met_data + (258, 0): ('name', BaseType.STRING, 'string', '', 1.0, 0.0), # dive_settings + (258, 1): ('model', BaseType.ENUM, 'tissue_model_type', '', 1.0, 0.0), # dive_settings + (258, 2): ('gf_low', BaseType.UINT8, 'uint8', 'percent', 1.0, 0.0), # dive_settings + (258, 3): ('gf_high', BaseType.UINT8, 'uint8', 'percent', 1.0, 0.0), # dive_settings + (258, 4): ('water_type', BaseType.ENUM, 'water_type', '', 1.0, 0.0), # dive_settings + (258, 5): ('water_density', BaseType.FLOAT32, 'float32', 'kg/m^3', 1.0, 0.0), # dive_settings + (258, 6): ('po2_warn', BaseType.UINT8, 'uint8', 'percent', 100.0, 0.0), # dive_settings + (258, 7): ('po2_critical', BaseType.UINT8, 'uint8', 'percent', 100.0, 0.0), # dive_settings + (258, 8): ('po2_deco', BaseType.UINT8, 'uint8', 'percent', 100.0, 0.0), # dive_settings + (258, 9): ('safety_stop_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # dive_settings + (258, 10): ('bottom_depth', BaseType.FLOAT32, 'float32', '', 1.0, 0.0), # dive_settings + (258, 11): ('bottom_time', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # dive_settings + (258, 12): ('apnea_countdown_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # dive_settings + (258, 13): ('apnea_countdown_time', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # dive_settings + (258, 14): ('backlight_mode', BaseType.ENUM, 'dive_backlight_mode', '', 1.0, 0.0), # dive_settings + (258, 15): ('backlight_brightness', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # dive_settings + (258, 16): ('backlight_timeout', BaseType.UINT8, 'backlight_timeout', '', 1.0, 0.0), # dive_settings + (258, 17): ('repeat_dive_interval', BaseType.UINT16, 'uint16', 's', 1.0, 0.0), # dive_settings + (258, 18): ('safety_stop_time', BaseType.UINT16, 'uint16', 's', 1.0, 0.0), # dive_settings + (258, 19): ('heart_rate_source_type', BaseType.ENUM, 'source_type', '', 1.0, 0.0), # dive_settings + (258, 20): ('heart_rate_source', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # dive_settings + (258, 21): ('travel_gas', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # dive_settings + (258, 22): ('ccr_low_setpoint_switch_mode', BaseType.ENUM, 'ccr_setpoint_switch_mode', '', 1.0, 0.0), # dive_settings + (258, 23): ('ccr_low_setpoint', BaseType.UINT8, 'uint8', 'percent', 100.0, 0.0), # dive_settings + (258, 24): ('ccr_low_setpoint_depth', BaseType.UINT32, 'uint32', 'm', 1000.0, 0.0), # dive_settings + (258, 25): ('ccr_high_setpoint_switch_mode', BaseType.ENUM, 'ccr_setpoint_switch_mode', '', 1.0, 0.0), # dive_settings + (258, 26): ('ccr_high_setpoint', BaseType.UINT8, 'uint8', 'percent', 100.0, 0.0), # dive_settings + (258, 27): ('ccr_high_setpoint_depth', BaseType.UINT32, 'uint32', 'm', 1000.0, 0.0), # dive_settings + (258, 29): ('gas_consumption_display', BaseType.ENUM, 'gas_consumption_rate_type', '', 1.0, 0.0), # dive_settings + (258, 30): ('up_key_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # dive_settings + (258, 35): ('dive_sounds', BaseType.ENUM, 'tone', '', 1.0, 0.0), # dive_settings + (258, 36): ('last_stop_multiple', BaseType.UINT8, 'uint8', '', 10.0, 0.0), # dive_settings + (258, 37): ('no_fly_time_mode', BaseType.ENUM, 'no_fly_time_mode', '', 1.0, 0.0), # dive_settings + (258, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # dive_settings + (258, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # dive_settings + (259, 0): ('helium_content', BaseType.UINT8, 'uint8', 'percent', 1.0, 0.0), # dive_gas + (259, 1): ('oxygen_content', BaseType.UINT8, 'uint8', 'percent', 1.0, 0.0), # dive_gas + (259, 2): ('status', BaseType.ENUM, 'dive_gas_status', '', 1.0, 0.0), # dive_gas + (259, 3): ('mode', BaseType.ENUM, 'dive_gas_mode', '', 1.0, 0.0), # dive_gas + (259, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # dive_gas + (262, 0): ('depth', BaseType.UINT32, 'uint32', 'm', 1000.0, 0.0), # dive_alarm + (262, 1): ('time', BaseType.SINT32, 'sint32', 's', 1.0, 0.0), # dive_alarm + (262, 2): ('enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # dive_alarm + (262, 3): ('alarm_type', BaseType.ENUM, 'dive_alarm_type', '', 1.0, 0.0), # dive_alarm + (262, 4): ('sound', BaseType.ENUM, 'tone', '', 1.0, 0.0), # dive_alarm + (262, 5): ('dive_types', BaseType.ENUM, 'sub_sport', '', 1.0, 0.0), # dive_alarm + (262, 6): ('id', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # dive_alarm + (262, 7): ('popup_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # dive_alarm + (262, 8): ('trigger_on_descent', BaseType.UINT8, 'bool', '', 1.0, 0.0), # dive_alarm + (262, 9): ('trigger_on_ascent', BaseType.UINT8, 'bool', '', 1.0, 0.0), # dive_alarm + (262, 10): ('repeating', BaseType.UINT8, 'bool', '', 1.0, 0.0), # dive_alarm + (262, 11): ('speed', BaseType.SINT32, 'sint32', 'mps', 1000.0, 0.0), # dive_alarm + (262, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # dive_alarm + (264, 0): ('exercise_category', BaseType.UINT16, 'exercise_category', '', 1.0, 0.0), # exercise_title + (264, 1): ('exercise_name', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # exercise_title + (264, 2): ('wkt_step_name', BaseType.STRING, 'string', '', 1.0, 0.0), # exercise_title + (264, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # exercise_title + (268, 0): ('reference_mesg', BaseType.UINT16, 'mesg_num', '', 1.0, 0.0), # dive_summary + (268, 1): ('reference_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # dive_summary + (268, 2): ('avg_depth', BaseType.UINT32, 'uint32', 'm', 1000.0, 0.0), # dive_summary + (268, 3): ('max_depth', BaseType.UINT32, 'uint32', 'm', 1000.0, 0.0), # dive_summary + (268, 4): ('surface_interval', BaseType.UINT32, 'uint32', 's', 1.0, 0.0), # dive_summary + (268, 5): ('start_cns', BaseType.UINT8, 'uint8', 'percent', 1.0, 0.0), # dive_summary + (268, 6): ('end_cns', BaseType.UINT8, 'uint8', 'percent', 1.0, 0.0), # dive_summary + (268, 7): ('start_n2', BaseType.UINT16, 'uint16', 'percent', 1.0, 0.0), # dive_summary + (268, 8): ('end_n2', BaseType.UINT16, 'uint16', 'percent', 1.0, 0.0), # dive_summary + (268, 9): ('o2_toxicity', BaseType.UINT16, 'uint16', 'OTUs', 1.0, 0.0), # dive_summary + (268, 10): ('dive_number', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # dive_summary + (268, 11): ('bottom_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # dive_summary + (268, 12): ('avg_pressure_sac', BaseType.UINT16, 'uint16', 'bar/min', 100.0, 0.0), # dive_summary + (268, 13): ('avg_volume_sac', BaseType.UINT16, 'uint16', 'L/min', 100.0, 0.0), # dive_summary + (268, 14): ('avg_rmv', BaseType.UINT16, 'uint16', 'L/min', 100.0, 0.0), # dive_summary + (268, 15): ('descent_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # dive_summary + (268, 16): ('ascent_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # dive_summary + (268, 17): ('avg_ascent_rate', BaseType.SINT32, 'sint32', 'm/s', 1000.0, 0.0), # dive_summary + (268, 22): ('avg_descent_rate', BaseType.UINT32, 'uint32', 'm/s', 1000.0, 0.0), # dive_summary + (268, 23): ('max_ascent_rate', BaseType.UINT32, 'uint32', 'm/s', 1000.0, 0.0), # dive_summary + (268, 24): ('max_descent_rate', BaseType.UINT32, 'uint32', 'm/s', 1000.0, 0.0), # dive_summary + (268, 25): ('hang_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # dive_summary + (268, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # dive_summary + (269, 0): ('reading_spo2', BaseType.UINT8, 'uint8', 'percent', 1.0, 0.0), # spo2_data + (269, 1): ('reading_confidence', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # spo2_data + (269, 2): ('mode', BaseType.ENUM, 'spo2_measurement_type', '', 1.0, 0.0), # spo2_data + (269, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # spo2_data + (275, 0): ('sleep_level', BaseType.ENUM, 'sleep_level', '', 1.0, 0.0), # sleep_level + (275, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # sleep_level + (285, 0): ('distance', BaseType.FLOAT32, 'float32', 'm', 1.0, 0.0), # jump + (285, 1): ('height', BaseType.FLOAT32, 'float32', 'm', 1.0, 0.0), # jump + (285, 2): ('rotations', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # jump + (285, 3): ('hang_time', BaseType.FLOAT32, 'float32', 's', 1.0, 0.0), # jump + (285, 4): ('score', BaseType.FLOAT32, 'float32', '', 1.0, 0.0), # jump + (285, 5): ('position_lat', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # jump + (285, 6): ('position_long', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # jump + (285, 7): ('speed', BaseType.UINT16, 'uint16', 'm/s', 1000.0, 0.0), # jump + (285, 8): ('enhanced_speed', BaseType.UINT32, 'uint32', 'm/s', 1000.0, 0.0), # jump + (285, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # jump + (289, 0): ('time', BaseType.UINT16, 'uint16', 's', 1.0, 0.0), # aad_accel_features + (289, 1): ('energy_total', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # aad_accel_features + (289, 2): ('zero_cross_cnt', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # aad_accel_features + (289, 3): ('instance', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # aad_accel_features + (289, 4): ('time_above_threshold', BaseType.UINT16, 'uint16', 's', 25.0, 0.0), # aad_accel_features + (289, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # aad_accel_features + (290, 0): ('timestamp_ms', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # beat_intervals + (290, 1): ('time', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # beat_intervals + (290, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # beat_intervals + (297, 0): ('respiration_rate', BaseType.SINT16, 'sint16', 'breaths/min', 100.0, 0.0), # respiration_rate + (297, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # respiration_rate + (302, 0): ('timestamp_ms', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # hsa_accelerometer_data + (302, 1): ('sampling_interval', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # hsa_accelerometer_data + (302, 2): ('accel_x', BaseType.SINT16, 'sint16', 'mG', 1.024, 0.0), # hsa_accelerometer_data + (302, 3): ('accel_y', BaseType.SINT16, 'sint16', 'mG', 1.024, 0.0), # hsa_accelerometer_data + (302, 4): ('accel_z', BaseType.SINT16, 'sint16', 'mG', 1.024, 0.0), # hsa_accelerometer_data + (302, 5): ('timestamp_32k', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # hsa_accelerometer_data + (302, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # hsa_accelerometer_data + (304, 0): ('processing_interval', BaseType.UINT16, 'uint16', 's', 1.0, 0.0), # hsa_step_data + (304, 1): ('steps', BaseType.UINT32, 'uint32', 'steps', 1.0, 0.0), # hsa_step_data + (304, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # hsa_step_data + (305, 0): ('processing_interval', BaseType.UINT16, 'uint16', 's', 1.0, 0.0), # hsa_spo2_data + (305, 1): ('reading_spo2', BaseType.UINT8, 'uint8', 'percent', 1.0, 0.0), # hsa_spo2_data + (305, 2): ('confidence', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # hsa_spo2_data + (305, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # hsa_spo2_data + (306, 0): ('processing_interval', BaseType.UINT16, 'uint16', 's', 1.0, 0.0), # hsa_stress_data + (306, 1): ('stress_level', BaseType.SINT8, 'sint8', 's', 1.0, 0.0), # hsa_stress_data + (306, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # hsa_stress_data + (307, 0): ('processing_interval', BaseType.UINT16, 'uint16', 's', 1.0, 0.0), # hsa_respiration_data + (307, 1): ('respiration_rate', BaseType.SINT16, 'sint16', 'breaths/min', 100.0, 0.0), # hsa_respiration_data + (307, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # hsa_respiration_data + (308, 0): ('processing_interval', BaseType.UINT16, 'uint16', 's', 1.0, 0.0), # hsa_heart_rate_data + (308, 1): ('status', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # hsa_heart_rate_data + (308, 2): ('heart_rate', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # hsa_heart_rate_data + (308, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # hsa_heart_rate_data + (312, 0): ('split_type', BaseType.ENUM, 'split_type', '', 1.0, 0.0), # split + (312, 1): ('total_elapsed_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # split + (312, 2): ('total_timer_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # split + (312, 3): ('total_distance', BaseType.UINT32, 'uint32', 'm', 100.0, 0.0), # split + (312, 4): ('avg_speed', BaseType.UINT32, 'uint32', 'm/s', 1000.0, 0.0), # split + (312, 9): ('start_time', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # split + (312, 13): ('total_ascent', BaseType.UINT16, 'uint16', 'm', 1.0, 0.0), # split + (312, 14): ('total_descent', BaseType.UINT16, 'uint16', 'm', 1.0, 0.0), # split + (312, 21): ('start_position_lat', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # split + (312, 22): ('start_position_long', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # split + (312, 23): ('end_position_lat', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # split + (312, 24): ('end_position_long', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # split + (312, 25): ('max_speed', BaseType.UINT32, 'uint32', 'm/s', 1000.0, 0.0), # split + (312, 26): ('avg_vert_speed', BaseType.SINT32, 'sint32', 'm/s', 1000.0, 0.0), # split + (312, 27): ('end_time', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # split + (312, 28): ('total_calories', BaseType.UINT32, 'uint32', 'kcal', 1.0, 0.0), # split + (312, 74): ('start_elevation', BaseType.UINT32, 'uint32', 'm', 5.0, 500.0), # split + (312, 78): ('active_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # split + (312, 110): ('total_moving_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # split + (312, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # split + (313, 0): ('split_type', BaseType.ENUM, 'split_type', '', 1.0, 0.0), # split_summary + (313, 3): ('num_splits', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # split_summary + (313, 4): ('total_timer_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # split_summary + (313, 5): ('total_distance', BaseType.UINT32, 'uint32', 'm', 100.0, 0.0), # split_summary + (313, 6): ('avg_speed', BaseType.UINT32, 'uint32', 'm/s', 1000.0, 0.0), # split_summary + (313, 7): ('max_speed', BaseType.UINT32, 'uint32', 'm/s', 1000.0, 0.0), # split_summary + (313, 8): ('total_ascent', BaseType.UINT16, 'uint16', 'm', 1.0, 0.0), # split_summary + (313, 9): ('total_descent', BaseType.UINT16, 'uint16', 'm', 1.0, 0.0), # split_summary + (313, 10): ('avg_heart_rate', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # split_summary + (313, 11): ('max_heart_rate', BaseType.UINT8, 'uint8', 'bpm', 1.0, 0.0), # split_summary + (313, 12): ('avg_vert_speed', BaseType.SINT32, 'sint32', 'm/s', 1000.0, 0.0), # split_summary + (313, 13): ('total_calories', BaseType.UINT32, 'uint32', 'kcal', 1.0, 0.0), # split_summary + (313, 65): ('active_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # split_summary + (313, 77): ('total_moving_time', BaseType.UINT32, 'uint32', 's', 1000.0, 0.0), # split_summary + (313, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # split_summary + (314, 0): ('processing_interval', BaseType.UINT16, 'uint16', 's', 1.0, 0.0), # hsa_body_battery_data + (314, 1): ('level', BaseType.SINT8, 'sint8', 'percent', 1.0, 0.0), # hsa_body_battery_data + (314, 2): ('charged', BaseType.SINT16, 'sint16', '', 1.0, 0.0), # hsa_body_battery_data + (314, 3): ('uncharged', BaseType.SINT16, 'sint16', '', 1.0, 0.0), # hsa_body_battery_data + (314, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # hsa_body_battery_data + (315, 0): ('event_id', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # hsa_event + (315, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # hsa_event + (317, 0): ('position_lat', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # climb_pro + (317, 1): ('position_long', BaseType.SINT32, 'sint32', 'degrees', 11930464.711111112, 0.0), # climb_pro + (317, 2): ('climb_pro_event', BaseType.ENUM, 'climb_pro_event', '', 1.0, 0.0), # climb_pro + (317, 3): ('climb_number', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # climb_pro + (317, 4): ('climb_category', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # climb_pro + (317, 5): ('current_dist', BaseType.FLOAT32, 'float32', 'm', 1.0, 0.0), # climb_pro + (317, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # climb_pro + (319, 0): ('sensor', BaseType.UINT32Z, 'ant_channel_id', '', 1.0, 0.0), # tank_update + (319, 1): ('pressure', BaseType.UINT16, 'uint16', 'bar', 100.0, 0.0), # tank_update + (319, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # tank_update + (323, 0): ('sensor', BaseType.UINT32Z, 'ant_channel_id', '', 1.0, 0.0), # tank_summary + (323, 1): ('start_pressure', BaseType.UINT16, 'uint16', 'bar', 100.0, 0.0), # tank_summary + (323, 2): ('end_pressure', BaseType.UINT16, 'uint16', 'bar', 100.0, 0.0), # tank_summary + (323, 3): ('volume_used', BaseType.UINT32, 'uint32', 'L', 100.0, 0.0), # tank_summary + (323, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # tank_summary + (346, 0): ('combined_awake_score', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # sleep_assessment + (346, 1): ('awake_time_score', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # sleep_assessment + (346, 2): ('awakenings_count_score', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # sleep_assessment + (346, 3): ('deep_sleep_score', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # sleep_assessment + (346, 4): ('sleep_duration_score', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # sleep_assessment + (346, 5): ('light_sleep_score', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # sleep_assessment + (346, 6): ('overall_sleep_score', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # sleep_assessment + (346, 7): ('sleep_quality_score', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # sleep_assessment + (346, 8): ('sleep_recovery_score', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # sleep_assessment + (346, 9): ('rem_sleep_score', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # sleep_assessment + (346, 10): ('sleep_restlessness_score', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # sleep_assessment + (346, 11): ('awakenings_count', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # sleep_assessment + (346, 14): ('interruptions_score', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # sleep_assessment + (346, 15): ('average_stress_during_sleep', BaseType.UINT16, 'uint16', '', 100.0, 0.0), # sleep_assessment + (370, 0): ('weekly_average', BaseType.UINT16, 'uint16', 'ms', 128.0, 0.0), # hrv_status_summary + (370, 1): ('last_night_average', BaseType.UINT16, 'uint16', 'ms', 128.0, 0.0), # hrv_status_summary + (370, 2): ('last_night_5_min_high', BaseType.UINT16, 'uint16', 'ms', 128.0, 0.0), # hrv_status_summary + (370, 3): ('baseline_low_upper', BaseType.UINT16, 'uint16', 'ms', 128.0, 0.0), # hrv_status_summary + (370, 4): ('baseline_balanced_lower', BaseType.UINT16, 'uint16', 'ms', 128.0, 0.0), # hrv_status_summary + (370, 5): ('baseline_balanced_upper', BaseType.UINT16, 'uint16', 'ms', 128.0, 0.0), # hrv_status_summary + (370, 6): ('status', BaseType.ENUM, 'hrv_status', '', 1.0, 0.0), # hrv_status_summary + (370, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # hrv_status_summary + (371, 0): ('value', BaseType.UINT16, 'uint16', 'ms', 128.0, 0.0), # hrv_value + (371, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # hrv_value + (372, 0): ('timestamp_ms', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # raw_bbi + (372, 1): ('data', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # raw_bbi + (372, 2): ('time', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # raw_bbi + (372, 3): ('quality', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # raw_bbi + (372, 4): ('gap', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # raw_bbi + (372, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # raw_bbi + (375, 0): ('device_index', BaseType.UINT8, 'device_index', '', 1.0, 0.0), # device_aux_battery_info + (375, 1): ('battery_voltage', BaseType.UINT16, 'uint16', 'V', 256.0, 0.0), # device_aux_battery_info + (375, 2): ('battery_status', BaseType.UINT8, 'battery_status', '', 1.0, 0.0), # device_aux_battery_info + (375, 3): ('battery_identifier', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # device_aux_battery_info + (375, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # device_aux_battery_info + (376, 0): ('timestamp_ms', BaseType.UINT16, 'uint16', 'ms', 1.0, 0.0), # hsa_gyroscope_data + (376, 1): ('sampling_interval', BaseType.UINT16, 'uint16', '1/32768 s', 1.0, 0.0), # hsa_gyroscope_data + (376, 2): ('gyro_x', BaseType.SINT16, 'sint16', 'deg/s', 28.57143, 0.0), # hsa_gyroscope_data + (376, 3): ('gyro_y', BaseType.SINT16, 'sint16', 'deg/s', 28.57143, 0.0), # hsa_gyroscope_data + (376, 4): ('gyro_z', BaseType.SINT16, 'sint16', 'deg/s', 28.57143, 0.0), # hsa_gyroscope_data + (376, 5): ('timestamp_32k', BaseType.UINT32, 'uint32', '1/32768 s', 1.0, 0.0), # hsa_gyroscope_data + (376, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # hsa_gyroscope_data + (387, 0): ('min_speed', BaseType.UINT32, 'uint32', 'm/s', 1000.0, 0.0), # chrono_shot_session + (387, 1): ('max_speed', BaseType.UINT32, 'uint32', 'm/s', 1000.0, 0.0), # chrono_shot_session + (387, 2): ('avg_speed', BaseType.UINT32, 'uint32', 'm/s', 1000.0, 0.0), # chrono_shot_session + (387, 3): ('shot_count', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # chrono_shot_session + (387, 4): ('projectile_type', BaseType.ENUM, 'projectile_type', '', 1.0, 0.0), # chrono_shot_session + (387, 5): ('grain_weight', BaseType.UINT32, 'uint32', 'gr', 10.0, 0.0), # chrono_shot_session + (387, 6): ('standard_deviation', BaseType.UINT32, 'uint32', 'm/s', 1000.0, 0.0), # chrono_shot_session + (387, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # chrono_shot_session + (388, 0): ('shot_speed', BaseType.UINT32, 'uint32', 'm/s', 1000.0, 0.0), # chrono_shot_data + (388, 1): ('shot_num', BaseType.UINT16, 'uint16', '', 1.0, 0.0), # chrono_shot_data + (388, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # chrono_shot_data + (389, 0): ('data', BaseType.BYTE, 'byte', '', 1.0, 0.0), # hsa_configuration_data + (389, 1): ('data_size', BaseType.UINT8, 'uint8', '', 1.0, 0.0), # hsa_configuration_data + (389, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # hsa_configuration_data + (393, 0): ('depth', BaseType.UINT32, 'uint32', 'm', 1000.0, 0.0), # dive_apnea_alarm + (393, 1): ('time', BaseType.SINT32, 'sint32', 's', 1.0, 0.0), # dive_apnea_alarm + (393, 2): ('enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # dive_apnea_alarm + (393, 3): ('alarm_type', BaseType.ENUM, 'dive_alarm_type', '', 1.0, 0.0), # dive_apnea_alarm + (393, 4): ('sound', BaseType.ENUM, 'tone', '', 1.0, 0.0), # dive_apnea_alarm + (393, 5): ('dive_types', BaseType.ENUM, 'sub_sport', '', 1.0, 0.0), # dive_apnea_alarm + (393, 6): ('id', BaseType.UINT32, 'uint32', '', 1.0, 0.0), # dive_apnea_alarm + (393, 7): ('popup_enabled', BaseType.UINT8, 'bool', '', 1.0, 0.0), # dive_apnea_alarm + (393, 8): ('trigger_on_descent', BaseType.UINT8, 'bool', '', 1.0, 0.0), # dive_apnea_alarm + (393, 9): ('trigger_on_ascent', BaseType.UINT8, 'bool', '', 1.0, 0.0), # dive_apnea_alarm + (393, 10): ('repeating', BaseType.UINT8, 'bool', '', 1.0, 0.0), # dive_apnea_alarm + (393, 11): ('speed', BaseType.SINT32, 'sint32', 'mps', 1000.0, 0.0), # dive_apnea_alarm + (393, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # dive_apnea_alarm + (398, 0): ('local_timestamp', BaseType.UINT32, 'local_date_time', '', 1.0, 0.0), # skin_temp_overnight + (398, 1): ('average_deviation', BaseType.FLOAT32, 'float32', '', 1.0, 0.0), # skin_temp_overnight + (398, 2): ('average_7_day_deviation', BaseType.FLOAT32, 'float32', '', 1.0, 0.0), # skin_temp_overnight + (398, 4): ('nightly_value', BaseType.FLOAT32, 'float32', '', 1.0, 0.0), # skin_temp_overnight + (398, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # skin_temp_overnight + (409, 0): ('processing_interval', BaseType.UINT16, 'uint16', 's', 1.0, 0.0), # hsa_wrist_temperature_data + (409, 1): ('value', BaseType.UINT16, 'uint16', 'C', 1000.0, 0.0), # hsa_wrist_temperature_data + (409, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # hsa_wrist_temperature_data + (412, 0): ('start_time', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # nap_event + (412, 1): ('start_timezone_offset', BaseType.SINT16, 'sint16', 'minutes', 1.0, 0.0), # nap_event + (412, 2): ('end_time', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # nap_event + (412, 3): ('end_timezone_offset', BaseType.SINT16, 'sint16', 'minutes', 1.0, 0.0), # nap_event + (412, 4): ('feedback', BaseType.ENUM, 'nap_period_feedback', '', 1.0, 0.0), # nap_event + (412, 5): ('is_deleted', BaseType.UINT8, 'bool', '', 1.0, 0.0), # nap_event + (412, 6): ('source', BaseType.ENUM, 'nap_source', '', 1.0, 0.0), # nap_event + (412, 7): ('update_timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # nap_event + (412, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # nap_event + (412, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # nap_event + (470, 0): ('severity', BaseType.ENUM, 'sleep_disruption_severity', '', 1.0, 0.0), # sleep_disruption_severity_period + (470, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # sleep_disruption_severity_period + (470, 254): ('message_index', BaseType.UINT16, 'message_index', '', 1.0, 0.0), # sleep_disruption_severity_period + (471, 0): ('severity', BaseType.ENUM, 'sleep_disruption_severity', '', 1.0, 0.0), # sleep_disruption_overnight_severity + (471, 253): ('timestamp', BaseType.UINT32, 'date_time', 'ms', 0.001, -631065600000.0), # sleep_disruption_overnight_severity +} + +PROFILE_FIELD_KEYS: frozenset[tuple[int, int]] = frozenset(PROFILE_FIELDS) + +# Closed enum types only (base type enum). Bitfields / open lists omitted. +PROFILE_ENUM_VALUES: dict[str, frozenset[int]] = { + 'activity': frozenset({0, 1}), + 'activity_level': frozenset({0, 1, 2}), + 'activity_subtype': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 254}), + 'activity_type': frozenset({0, 1, 2, 3, 4, 5, 6, 8, 254}), + 'analog_watchface_layout': frozenset({0, 1, 2}), + 'ant_network': frozenset({0, 1, 2, 3}), + 'attitude_stage': frozenset({0, 1, 2, 3}), + 'auto_sync_frequency': frozenset({0, 1, 2, 3, 4}), + 'autolap_trigger': frozenset({0, 1, 2, 3, 4, 5, 6, 13}), + 'autoscroll': frozenset({0, 1, 2, 3}), + 'backlight_mode': frozenset({0, 1, 2, 3, 4, 5, 6}), + 'bike_light_network_config_type': frozenset({0, 4, 5, 6}), + 'body_location': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}), + 'bp_status': frozenset({0, 1, 2, 3, 4}), + 'camera_event_type': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14}), + 'camera_orientation_type': frozenset({0, 1, 2, 3}), + 'ccr_setpoint_switch_mode': frozenset({0, 1}), + 'climb_pro_event': frozenset({0, 1, 2}), + 'course_point': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53}), + 'date_mode': frozenset({0, 1}), + 'day_of_week': frozenset({0, 1, 2, 3, 4, 5, 6}), + 'digital_watchface_layout': frozenset({0, 1, 2}), + 'display_heart': frozenset({0, 1, 2}), + 'display_measure': frozenset({0, 1, 2}), + 'display_orientation': frozenset({0, 1, 2, 3, 4}), + 'display_position': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41}), + 'display_power': frozenset({0, 1}), + 'dive_alarm_type': frozenset({0, 1, 2}), + 'dive_alert': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35, 36, 37, 38, 39}), + 'dive_backlight_mode': frozenset({0, 1}), + 'dive_gas_mode': frozenset({0, 1}), + 'dive_gas_status': frozenset({0, 1, 2}), + 'event': frozenset({0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 32, 33, 36, 42, 43, 44, 45, 46, 47, 54, 56, 57, 71, 72, 73, 75, 76, 81, 82}), + 'event_type': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}), + 'exd_data_units': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49}), + 'exd_descriptors': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96}), + 'exd_display_type': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}), + 'exd_layout': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8}), + 'exd_qualifiers': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 242, 243, 244, 245, 246, 247, 248, 249, 250}), + 'file': frozenset({1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 14, 15, 20, 28, 32, 34, 35, 40, 247, 254}), + 'fitness_equipment_state': frozenset({0, 1, 2, 3}), + 'gas_consumption_rate_type': frozenset({0, 1, 2}), + 'gender': frozenset({0, 1}), + 'goal': frozenset({0, 1, 2, 3, 4, 5, 6}), + 'goal_recurrence': frozenset({0, 1, 2, 3, 4, 5}), + 'goal_source': frozenset({0, 1, 2}), + 'hr_type': frozenset({0, 1}), + 'hr_zone_calc': frozenset({0, 1, 2, 3}), + 'hrv_status': frozenset({0, 1, 2, 3, 4}), + 'intensity': frozenset({0, 1, 2, 3, 4, 5, 6}), + 'language': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 254}), + 'lap_trigger': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8}), + 'length_type': frozenset({0, 1}), + 'max_met_category': frozenset({0, 1}), + 'max_met_heart_rate_source': frozenset({0, 1}), + 'max_met_speed_source': frozenset({0, 1, 2}), + 'mesg_count': frozenset({0, 1, 2}), + 'nap_period_feedback': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}), + 'nap_source': frozenset({0, 1, 2}), + 'no_fly_time_mode': frozenset({0, 1}), + 'power_phase_type': frozenset({0, 1, 2, 3}), + 'projectile_type': frozenset({0, 1, 2, 3, 4, 5}), + 'pwr_zone_calc': frozenset({0, 1}), + 'radar_threat_level_type': frozenset({0, 1, 2, 3}), + 'rider_position_type': frozenset({0, 1, 2, 3}), + 'schedule': frozenset({0, 1}), + 'segment_delete_status': frozenset({0, 1, 2}), + 'segment_lap_status': frozenset({0, 1}), + 'segment_leaderboard_type': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}), + 'segment_selection_type': frozenset({0, 1}), + 'sensor_type': frozenset({0, 1, 2, 3}), + 'session_trigger': frozenset({0, 1, 2, 3}), + 'side': frozenset({0, 1}), + 'sleep_disruption_severity': frozenset({0, 1, 2, 3}), + 'sleep_level': frozenset({0, 1, 2, 3, 4}), + 'source_type': frozenset({0, 1, 2, 3, 4, 5}), + 'split_type': frozenset({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 21, 22, 23, 28, 29}), + 'spo2_measurement_type': frozenset({0, 1, 2, 3}), + 'sport': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 53, 56, 58, 59, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 254}), + 'sport_event': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8}), + 'stroke_type': frozenset({0, 1, 2, 3, 4, 5}), + 'sub_sport': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 62, 63, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 121, 123, 124, 125, 126, 127, 254}), + 'swim_stroke': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8}), + 'switch': frozenset({0, 1, 2}), + 'tap_sensitivity': frozenset({0, 1, 2}), + 'time_mode': frozenset({0, 1, 2, 3, 4, 5}), + 'time_zone': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 253, 254}), + 'timer_trigger': frozenset({0, 1, 2}), + 'tissue_model_type': frozenset({0}), + 'tone': frozenset({0, 1, 2, 3}), + 'turn_type': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37}), + 'watchface_mode': frozenset({0, 1, 2, 3}), + 'water_type': frozenset({0, 1, 2, 3}), + 'weather_report': frozenset({0, 1, 2}), + 'weather_severe_type': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84}), + 'weather_severity': frozenset({0, 1, 2, 3, 4}), + 'weather_status': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}), + 'wkt_step_duration': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31}), + 'wkt_step_target': frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}), + 'workout_equipment': frozenset({0, 1, 2, 3, 4, 5}), +} + +PROFILE_ENUM_FIELD_COUNT = 153 + diff --git a/fit_tool/tests/test_profile_scope.py b/fit_tool/tests/test_profile_scope.py new file mode 100644 index 0000000..8f9e6f0 --- /dev/null +++ b/fit_tool/tests/test_profile_scope.py @@ -0,0 +1,293 @@ +"""PROFILE scope selection (CORE / DOMAIN / FULL) and catalog-backed rules.""" + +from __future__ import annotations + +import unittest +from pathlib import Path + +from fit_tool.base_type import BaseType +from fit_tool.definition_message import DefinitionMessage +from fit_tool.field_definition import FieldDefinition +from fit_tool.fit_file import FitFile +from fit_tool.fit_file_builder import FitFileBuilder +from fit_tool.profile.field_catalog import ( + PROFILE_ENUM_FIELD_COUNT, + PROFILE_ENUM_TYPE_COUNT, + PROFILE_FIELD_COUNT, + PROFILE_FIELDS, + PROFILE_MESSAGE_COUNT, + PROFILE_SDK_VERSION, +) +from fit_tool.profile.messages.activity_message import ActivityMessage +from fit_tool.profile.messages.file_id_message import FileIdMessage +from fit_tool.profile.messages.lap_message import LapMessage +from fit_tool.profile.messages.record_message import RecordMessage +from fit_tool.profile.messages.session_message import SessionMessage +from fit_tool.profile.profile_type import FileType, Manufacturer, Sport +from fit_tool.record import Record +from fit_tool.validation import ( + DEFAULT_PROFILE_SCOPE, + DOMAIN_MESSAGE_IDS, + ConformanceLevel, + ProfileScope, + profile_rule_coverage, + validate_fit_file, +) + +DATA_DIR = Path(__file__).resolve().parent / 'data' +SDK_DIR = DATA_DIR / 'sdk' + + +def _minimal_activity_builder() -> FitFileBuilder: + builder = FitFileBuilder() + file_id = FileIdMessage() + file_id.type = FileType.ACTIVITY + file_id.manufacturer = Manufacturer.DEVELOPMENT.value + file_id.product = 0 + file_id.serial_number = 1234 + file_id.time_created = 1_700_000_000_000 + builder.add(file_id) + + record = RecordMessage() + record.timestamp = 1_700_000_000_000 + builder.add(record) + + lap = LapMessage() + lap.message_index = 0 + lap.timestamp = 1_700_000_001_000 + lap.start_time = 1_700_000_000_000 + lap.total_elapsed_time = 1 + lap.total_timer_time = 1 + builder.add(lap) + + session = SessionMessage() + session.message_index = 0 + session.timestamp = 1_700_000_001_000 + session.start_time = 1_700_000_000_000 + session.total_elapsed_time = 1 + session.total_timer_time = 1 + session.sport = Sport.CYCLING + session.first_lap_index = 0 + session.num_laps = 1 + builder.add(session) + + activity = ActivityMessage() + activity.timestamp = 1_700_000_001_000 + activity.num_sessions = 1 + activity.total_timer_time = 1 + builder.add(activity) + return builder + + +class TestProfileCatalog(unittest.TestCase): + def test_catalog_matches_sdk_version_and_counts(self): + from fit_tool import SDK_VERSION + + self.assertEqual(PROFILE_SDK_VERSION, SDK_VERSION) + self.assertEqual(PROFILE_MESSAGE_COUNT, 123) + self.assertEqual(PROFILE_FIELD_COUNT, 1406) + self.assertEqual(PROFILE_ENUM_TYPE_COUNT, 100) + self.assertEqual(PROFILE_ENUM_FIELD_COUNT, 153) + self.assertEqual(len(PROFILE_FIELDS), PROFILE_FIELD_COUNT) + + def test_file_id_type_in_catalog(self): + entry = PROFILE_FIELDS[(0, 0)] + name, base_type, type_name, _units, _scale, _offset = entry + self.assertEqual(name, 'type') + self.assertEqual(base_type, BaseType.ENUM) + self.assertEqual(type_name, 'file') + + +class TestProfileRuleCoverage(unittest.TestCase): + def test_default_scope_is_core(self): + self.assertIs(DEFAULT_PROFILE_SCOPE, ProfileScope.CORE) + core = profile_rule_coverage() + self.assertEqual(core['scope'], 'core') + self.assertTrue(core['default_for_strict']) + self.assertEqual(core['native_fields_in_scope'], 0) + self.assertEqual(core['field_coverage_pct'], 0.0) + self.assertIn('developer_fields', core['rule_families']) + self.assertNotIn('native_base_type', core['rule_families']) + + def test_domain_and_full_coverage(self): + domain = profile_rule_coverage(ProfileScope.DOMAIN) + full = profile_rule_coverage(ProfileScope.FULL) + + self.assertEqual(domain['scope'], 'domain') + self.assertFalse(domain['default_for_strict']) + self.assertEqual(domain['native_messages_in_scope'], len(DOMAIN_MESSAGE_IDS)) + self.assertGreater(domain['native_fields_in_scope'], 0) + self.assertLess(domain['field_coverage_pct'], 100.0) + self.assertIn('native_base_type', domain['rule_families']) + self.assertIn('closed_enum_values', domain['rule_families']) + + self.assertEqual(full['scope'], 'full') + self.assertFalse(full['default_for_strict']) + self.assertEqual(full['native_messages_in_scope'], PROFILE_MESSAGE_COUNT) + self.assertEqual(full['native_fields_in_scope'], PROFILE_FIELD_COUNT) + self.assertEqual(full['field_coverage_pct'], 100.0) + self.assertEqual(full['message_coverage_pct'], 100.0) + self.assertEqual(full['enum_fields_in_scope'], PROFILE_ENUM_FIELD_COUNT) + self.assertEqual(full['enum_field_coverage_pct'], 100.0) + + +class TestProfileScopeValidation(unittest.TestCase): + def test_core_is_default_and_skips_native_base_type(self): + # Definition with wrong base type for record.heart_rate (field 3 is UINT8). + definition = DefinitionMessage( + local_id=0, + global_id=20, + field_definitions=[ + FieldDefinition(field_id=253, size=4, base_type=BaseType.UINT32), + FieldDefinition(field_id=3, size=2, base_type=BaseType.UINT16), + ], + ) + records = [Record.from_message(definition)] + + core = validate_fit_file(records, levels={ConformanceLevel.PROFILE}) + self.assertFalse(core.has_errors) + + domain = validate_fit_file( + records, + levels={ConformanceLevel.PROFILE}, + profile_scope=ProfileScope.DOMAIN, + ) + self.assertTrue(domain.has_errors) + self.assertTrue( + any('base type' in finding.message and 'UINT16' in finding.message + for finding in domain.errors) + ) + + def test_domain_accepts_matching_base_types(self): + definition = DefinitionMessage( + local_id=0, + global_id=20, + field_definitions=[ + FieldDefinition(field_id=253, size=4, base_type=BaseType.UINT32), + FieldDefinition(field_id=3, size=1, base_type=BaseType.UINT8), + ], + ) + report = validate_fit_file( + [Record.from_message(definition)], + levels={ConformanceLevel.PROFILE}, + profile_scope=ProfileScope.DOMAIN, + ) + self.assertFalse(report.has_errors) + + def test_closed_enum_rejects_unknown_value(self): + file_id = FileIdMessage() + file_id.type = 250 # not a Profile file enum value + file_id.manufacturer = Manufacturer.DEVELOPMENT.value + file_id.product = 0 + file_id.serial_number = 1 + file_id.time_created = 1_700_000_000_000 + + core = validate_fit_file( + [Record.from_message(file_id)], + levels={ConformanceLevel.PROFILE}, + ) + self.assertFalse(core.has_errors) + + domain = validate_fit_file( + [Record.from_message(file_id)], + levels={ConformanceLevel.PROFILE}, + profile_scope=ProfileScope.DOMAIN, + ) + self.assertTrue(domain.has_errors) + self.assertTrue( + any('outside Profile enum' in finding.message for finding in domain.errors) + ) + + def test_closed_enum_accepts_valid_value(self): + file_id = FileIdMessage() + file_id.type = FileType.ACTIVITY + file_id.manufacturer = Manufacturer.DEVELOPMENT.value + file_id.product = 0 + file_id.serial_number = 1 + file_id.time_created = 1_700_000_000_000 + + report = validate_fit_file( + [Record.from_message(file_id)], + levels={ConformanceLevel.PROFILE}, + profile_scope=ProfileScope.DOMAIN, + ) + self.assertFalse(report.has_errors) + + def test_full_scope_covers_non_domain_message(self): + # course is not in DOMAIN_MESSAGE_IDS; base-type mismatch only fires under FULL. + course_global_id = 31 + self.assertNotIn(course_global_id, DOMAIN_MESSAGE_IDS) + # course.sport is field 4, Profile base type ENUM. + definition = DefinitionMessage( + local_id=0, + global_id=course_global_id, + field_definitions=[ + FieldDefinition(field_id=4, size=1, base_type=BaseType.UINT8), # wrong: Profile is ENUM + ], + ) + records = [Record.from_message(definition)] + + domain = validate_fit_file( + records, + levels={ConformanceLevel.PROFILE}, + profile_scope=ProfileScope.DOMAIN, + ) + self.assertFalse(domain.has_errors) + + full = validate_fit_file( + records, + levels={ConformanceLevel.PROFILE}, + profile_scope=ProfileScope.FULL, + ) + self.assertTrue(full.has_errors) + self.assertTrue(any('base type' in f.message for f in full.errors)) + + def test_minimal_activity_passes_domain_and_full(self): + fit_file = _minimal_activity_builder().build() + for scope in (ProfileScope.CORE, ProfileScope.DOMAIN, ProfileScope.FULL): + report = validate_fit_file( + fit_file, + levels={ConformanceLevel.PROFILE}, + profile_scope=scope, + ) + self.assertFalse(report.has_errors, msg=f'scope={scope}: {report.findings}') + + def test_sdk_activity_passes_full_profile_scope(self): + path = SDK_DIR / 'Activity.fit' + if not path.is_file(): + self.skipTest('SDK Activity.fit fixture missing') + fit_file = FitFile.from_file(str(path)) + report = validate_fit_file( + fit_file, + levels={ConformanceLevel.PROFILE}, + profile_scope=ProfileScope.FULL, + ) + self.assertFalse(report.has_errors, msg=report.findings) + + def test_fit_file_validate_forwards_profile_scope(self): + fit_file = _minimal_activity_builder().build() + report = fit_file.validate( + levels={ConformanceLevel.PROFILE}, + profile_scope=ProfileScope.DOMAIN, + ) + self.assertFalse(report.has_errors) + + def test_invalid_profile_scope_type_raises(self): + with self.assertRaisesRegex(Exception, 'ProfileScope'): + validate_fit_file( + [], + levels={ConformanceLevel.PROFILE}, + profile_scope='full', # type: ignore[arg-type] + ) + + def test_public_exports(self): + from fit_tool import ProfileScope as RootScope + from fit_tool import profile_rule_coverage as root_coverage + + self.assertIs(RootScope, ProfileScope) + self.assertIs(root_coverage, profile_rule_coverage) + self.assertEqual(RootScope.FULL.value, 'full') + + +if __name__ == '__main__': + unittest.main() diff --git a/fit_tool/tests/test_public_api.py b/fit_tool/tests/test_public_api.py index 19205e0..0ad3273 100644 --- a/fit_tool/tests/test_public_api.py +++ b/fit_tool/tests/test_public_api.py @@ -23,9 +23,11 @@ def test_core_symbols_importable_from_package_root(self) -> None: FitParseError, FitRecordError, FitValidationError, + ProfileScope, Severity, ValidationFinding, ValidationReport, + profile_rule_coverage, validate_fit_file, ) @@ -49,6 +51,9 @@ def test_core_symbols_importable_from_package_root(self) -> None: self.assertEqual(EncodeMode.PRESERVE.value, 'preserve') self.assertEqual(EncodeMode.CANONICAL.value, 'canonical') self.assertIsInstance(EncodeOptions(), EncodeOptions) + self.assertEqual(ProfileScope.CORE.value, 'core') + self.assertEqual(ProfileScope.FULL.value, 'full') + self.assertTrue(callable(profile_rule_coverage)) def test_package_all_matches_api_surface(self) -> None: import fit_tool diff --git a/fit_tool/validation.py b/fit_tool/validation.py index f203cae..cad5cc8 100644 --- a/fit_tool/validation.py +++ b/fit_tool/validation.py @@ -6,18 +6,18 @@ Levels (aligned with ``docs/FIT_CONFORMANCE_DESIGN.md``): * **WIRE** — local IDs, definition layout, data-record size vs definition -* **PROFILE** — developer-field declarations (``developer_data_id`` / - ``field_description``) plus **ambiguous native subfield** matches. - This is **not** full Garmin Profile validation (enums, units, required - native fields per message, and broader subfield rule families remain deferred). -* **FILE_TYPE** — ``file_id`` rules plus Activity, Workout, and Course - required messages/fields +* **PROFILE** — Profile semantics under a selectable :class:`ProfileScope` + (CORE / DOMAIN / FULL). Default scope is **CORE** (developer-field rules + + ambiguous native subfields). DOMAIN and FULL add data-driven native base-type + and closed-enum checks from the gen-exported field catalog (design doc §3.1 O1). + FULL is **opt-in**, never the default for ``strict=True`` / :data:`DEFAULT_LEVELS`. +* **FILE_TYPE** — ``file_id`` rules and Activity required messages/fields * **PRESERVATION** — opt-in checks for post-edit rewrite loss (e.g. unknown field ``raw_bytes`` cleared). Not part of default / strict levels. -File-type rules are implemented for **Activity**, **Workout**, and **Course**. -Other ``file_id.type`` values fail closed at the FILE_TYPE level (intentional -until more validators exist). +File-type rules are implemented only for **Activity**. Other ``file_id.type`` +values fail closed at the FILE_TYPE level (intentional until more validators +exist). """ from __future__ import annotations @@ -34,6 +34,15 @@ from fit_tool.exceptions import FitValidationError from fit_tool.field import UnknownField from fit_tool.message import Message +from fit_tool.profile.field_catalog import ( + PROFILE_ENUM_FIELD_COUNT, + PROFILE_ENUM_TYPE_COUNT, + PROFILE_ENUM_VALUES, + PROFILE_FIELD_COUNT, + PROFILE_FIELDS, + PROFILE_MESSAGE_COUNT, + PROFILE_SDK_VERSION, +) from fit_tool.profile.profile_type import Event, EventType, FileType, MesgNum, WorkoutStepDuration from fit_tool.record import Record @@ -61,6 +70,19 @@ EventType.STOP_DISABLE_ALL.value, }) +# High-frequency Activity / Workout messages for PROFILE DOMAIN scope (§3.1 M4). +DOMAIN_MESSAGE_IDS: frozenset[int] = frozenset({ + MesgNum.FILE_ID.value, # 0 + MesgNum.SESSION.value, # 18 + MesgNum.LAP.value, # 19 + MesgNum.RECORD.value, # 20 + MesgNum.EVENT.value, # 21 + MesgNum.DEVICE_INFO.value, # 23 + MesgNum.WORKOUT.value, # 26 + MesgNum.WORKOUT_STEP.value, # 27 + MesgNum.ACTIVITY.value, # 34 +}) + class ConformanceLevel(Enum): """Independently selectable validation dimensions.""" @@ -71,6 +93,22 @@ class ConformanceLevel(Enum): PRESERVATION = 'preservation' +class ProfileScope(Enum): + """Depth of :attr:`ConformanceLevel.PROFILE` rules (design doc §3.1 O1). + + * **CORE** — developer-field declarations + ambiguous subfield ERROR. + Default for :data:`DEFAULT_LEVELS` / Builder ``strict=True``. + * **DOMAIN** — CORE plus native base-type and closed-enum checks on + high-frequency Activity/Workout messages (:data:`DOMAIN_MESSAGE_IDS`). + * **FULL** — CORE plus the same native rules for **all** messages in the + bundled Profile field catalog. Explicit opt-in only. + """ + + CORE = 'core' + DOMAIN = 'domain' + FULL = 'full' + + class Severity(Enum): """Finding severity at a conformance level.""" @@ -89,6 +127,9 @@ class Severity(Enum): # Builder(strict=True) and FitFileValidator().validate() use all default levels. STRICT_LEVELS = DEFAULT_LEVELS +# PROFILE scope for DEFAULT_LEVELS / strict. FULL must never be the default. +DEFAULT_PROFILE_SCOPE = ProfileScope.CORE + @dataclass(frozen=True) class ValidationFinding: @@ -254,6 +295,83 @@ def _error( ) +def _normalize_profile_scope(scope: ProfileScope | None) -> ProfileScope: + if scope is None: + return DEFAULT_PROFILE_SCOPE + if not isinstance(scope, ProfileScope): + raise FitValidationError( + f'profile_scope must be a ProfileScope, got {type(scope).__name__}' + ) + return scope + + +def _message_ids_for_scope(scope: ProfileScope) -> frozenset[int] | None: + """Return global message ids in scope, or ``None`` when scope is CORE (no native catalog).""" + if scope is ProfileScope.CORE: + return None + if scope is ProfileScope.DOMAIN: + return DOMAIN_MESSAGE_IDS + # FULL: every message that appears in the catalog. + return frozenset(global_id for global_id, _field_id in PROFILE_FIELDS) + + +def profile_rule_coverage(scope: ProfileScope | None = None) -> dict[str, Any]: + """Publish PROFILE validation coverage metrics for *scope*. + + Used by docs/tests and release notes. Percentages are relative to the full + bundled Profile field catalog (``PROFILE_*`` constants from gen export). + """ + selected = _normalize_profile_scope(scope) + message_ids = _message_ids_for_scope(selected) + + if message_ids is None: + native_messages = 0 + native_fields = 0 + enum_fields = 0 + rule_families = ( + 'developer_fields', + 'subfield_ambiguity', + ) + else: + native_messages = len({gid for gid in message_ids if any( + key[0] == gid for key in PROFILE_FIELDS + )}) + native_fields = sum(1 for key in PROFILE_FIELDS if key[0] in message_ids) + enum_fields = sum( + 1 + for key, meta in PROFILE_FIELDS.items() + if key[0] in message_ids and meta[2] in PROFILE_ENUM_VALUES + ) + rule_families = ( + 'developer_fields', + 'subfield_ambiguity', + 'native_base_type', + 'closed_enum_values', + ) + + def _pct(part: int, whole: int) -> float: + if whole <= 0: + return 0.0 + return round(100.0 * part / whole, 2) + + return { + 'scope': selected.value, + 'profile_sdk_version': PROFILE_SDK_VERSION, + 'rule_families': list(rule_families), + 'full_messages': PROFILE_MESSAGE_COUNT, + 'full_fields': PROFILE_FIELD_COUNT, + 'full_enum_types': PROFILE_ENUM_TYPE_COUNT, + 'full_enum_fields': PROFILE_ENUM_FIELD_COUNT, + 'native_messages_in_scope': native_messages, + 'native_fields_in_scope': native_fields, + 'enum_fields_in_scope': enum_fields, + 'message_coverage_pct': _pct(native_messages, PROFILE_MESSAGE_COUNT), + 'field_coverage_pct': _pct(native_fields, PROFILE_FIELD_COUNT), + 'enum_field_coverage_pct': _pct(enum_fields, PROFILE_ENUM_FIELD_COUNT), + 'default_for_strict': selected is DEFAULT_PROFILE_SCOPE, + } + + def _collect_wire_findings(records: Sequence[Record], findings: list[ValidationFinding]) -> None: active_definitions = {} for record_index, record in enumerate(records): @@ -289,6 +407,36 @@ def _collect_wire_findings(records: Sequence[Record], findings: list[ValidationF def _collect_profile_findings( + records: Sequence[Record], + data_messages: Sequence[DataMessage], + findings: list[ValidationFinding], + data_message_indices: Mapping[int, int], + profile_scope: ProfileScope, +) -> None: + # CORE (always when PROFILE is selected). + _collect_developer_field_findings(data_messages, findings, data_message_indices) + for message_pos, message in enumerate(data_messages): + _collect_subfield_ambiguity_findings( + message, + findings, + data_message_indices.get(message_pos), + ) + + message_ids = _message_ids_for_scope(profile_scope) + if message_ids is None: + return + + # DOMAIN / FULL: data-driven native rules from gen-exported field catalog. + _collect_native_base_type_findings(records, findings, message_ids) + _collect_closed_enum_findings( + data_messages, + findings, + data_message_indices, + message_ids, + ) + + +def _collect_developer_field_findings( data_messages: Sequence[DataMessage], findings: list[ValidationFinding], data_message_indices: Mapping[int, int], @@ -384,8 +532,6 @@ def _collect_profile_findings( record_index, ) - _collect_subfield_ambiguity_findings(message, findings, record_index) - def _collect_subfield_ambiguity_findings( message: DataMessage, @@ -417,6 +563,90 @@ def _collect_subfield_ambiguity_findings( ) +def _collect_native_base_type_findings( + records: Sequence[Record], + findings: list[ValidationFinding], + message_ids: frozenset[int], +) -> None: + """ERROR when a definition declares a base type that differs from Profile. + + Only main fields present in the gen-exported catalog are checked. Unknown + field ids (not in Profile for that message) are ignored here — they belong + to unknown-field / PRESERVATION work. + """ + for record_index, record in enumerate(records): + message = record.message + if not isinstance(message, DefinitionMessage): + continue + if message.global_id not in message_ids: + continue + for field_def in message.field_definitions: + key = (message.global_id, field_def.field_id) + catalog_entry = PROFILE_FIELDS.get(key) + if catalog_entry is None: + continue + field_name, expected_base_type, _type_name, _units, _scale, _offset = catalog_entry + if field_def.base_type is expected_base_type: + continue + _error( + findings, + ConformanceLevel.PROFILE, + ( + f'Native field {field_name!r} (id {field_def.field_id}) on global ' + f'message {message.global_id} declares base type ' + f'{field_def.base_type.name}, but Profile requires ' + f'{expected_base_type.name}.' + ), + record_index, + ) + + +def _collect_closed_enum_findings( + data_messages: Sequence[DataMessage], + findings: list[ValidationFinding], + data_message_indices: Mapping[int, int], + message_ids: frozenset[int], +) -> None: + """ERROR when a closed Profile enum field holds an unknown value. + + Only Types-sheet entries with base type ``enum`` are checked (closed sets). + Open lists (manufacturer, garmin_product, …) and bitfields are excluded from + the catalog. Invalid FIT sentinels are skipped. + """ + for message_pos, message in enumerate(data_messages): + if message.global_id not in message_ids: + continue + record_index = data_message_indices.get(message_pos) + for field in getattr(message, 'fields', None) or []: + if not field.is_valid(): + continue + key = (message.global_id, field.field_id) + catalog_entry = PROFILE_FIELDS.get(key) + if catalog_entry is None: + continue + field_name, _base_type, type_name, _units, _scale, _offset = catalog_entry + if not type_name: + continue + allowed = PROFILE_ENUM_VALUES.get(type_name) + if allowed is None: + continue + invalid_raw = field.base_type.invalid_raw_value() + for encoded in field.encoded_values: + if encoded is None or encoded == invalid_raw: + continue + if encoded in allowed: + continue + _error( + findings, + ConformanceLevel.PROFILE, + ( + f'Native field {message.name}.{field_name} has value ' + f'{encoded!r} outside Profile enum {type_name!r}.' + ), + record_index, + ) + + def _require_fields_findings( findings: list[ValidationFinding], message: DataMessage, @@ -870,6 +1100,7 @@ def validate_fit_file( source: FitFile | Sequence[Record], levels: Iterable[ConformanceLevel] | None = None, *, + profile_scope: ProfileScope | None = None, raise_on_error: bool = False, ) -> ValidationReport: """Validate a :class:`~fit_tool.fit_file.FitFile` or ordered record list. @@ -882,6 +1113,12 @@ def validate_fit_file( Conformance levels to run. Defaults to WIRE + PROFILE + FILE_TYPE. Pass ``{ConformanceLevel.PRESERVATION}`` (or include it) for opt-in post-edit loss checks. PRESERVATION is **not** in the default set. + profile_scope: + Depth of PROFILE rules when :attr:`ConformanceLevel.PROFILE` is selected. + Defaults to :data:`DEFAULT_PROFILE_SCOPE` (**CORE**). Pass + :attr:`ProfileScope.DOMAIN` or :attr:`ProfileScope.FULL` for native + field/enum checks from the gen-exported Profile catalog. FULL is never + the default for Builder ``strict=True``. raise_on_error: If true, raise :class:`FitValidationError` when any ERROR findings exist (first error message is used, matching historical Builder strict behavior). @@ -892,6 +1129,7 @@ def validate_fit_file( Collected findings. Truthy when there are no errors. """ selected = _normalize_levels(levels) + selected_scope = _normalize_profile_scope(profile_scope) records = _records_from_source(source) findings: list[ValidationFinding] = [] @@ -905,7 +1143,13 @@ def validate_fit_file( if ConformanceLevel.WIRE in selected: _collect_wire_findings(records, findings) if ConformanceLevel.PROFILE in selected: - _collect_profile_findings(data_messages, findings, data_message_indices) + _collect_profile_findings( + records, + data_messages, + findings, + data_message_indices, + selected_scope, + ) if ConformanceLevel.FILE_TYPE in selected: _collect_file_type_findings(data_messages, findings, data_message_indices) if ConformanceLevel.PRESERVATION in selected: diff --git a/news/SHA-20.feature b/news/SHA-20.feature new file mode 100644 index 0000000..caee514 --- /dev/null +++ b/news/SHA-20.feature @@ -0,0 +1,8 @@ +PROFILE validation now supports selectable scopes (``ProfileScope.CORE`` / +``DOMAIN`` / ``FULL``) under architecture decision O1. Default ``strict`` / +``DEFAULT_LEVELS`` remain **CORE** (developer fields + ambiguous subfields). +DOMAIN and FULL add data-driven native base-type and closed-enum checks from a +gen-exported field catalog (``fit_tool.profile.field_catalog``) derived from +bundled Profile.xlsx ``21.205.0``. FULL is opt-in only; use +``validate_fit_file(..., profile_scope=ProfileScope.FULL)`` or +``profile_rule_coverage(ProfileScope.FULL)`` for coverage metrics.