python-fitparse 2.7.0__tar.gz → 2.10.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: python-fitparse
3
- Version: 2.7.0
3
+ Version: 2.10.0
4
4
  Summary: Python library to parse ANT/Garmin .FIT files
5
5
  Project-URL: Homepage, https://github.com/nbr23/python-fitparse
6
6
  Project-URL: Repository, https://github.com/nbr23/python-fitparse
@@ -62,10 +62,28 @@ class DeveloperDataMixin:
62
62
  dev_data_index = message.get_raw_value('developer_data_index')
63
63
  field_def_num = message.get_raw_value('field_definition_number')
64
64
  base_type_id = message.get_raw_value('fit_base_type_id')
65
- field_name = message.get_raw_value('field_name') or "unnamed_dev_field_%s" % field_def_num
65
+ field_name = message.get_raw_value('field_name')
66
66
  units = message.get_raw_value("units")
67
67
  native_field_num = message.get_raw_value('native_field_num')
68
68
 
69
+ for name, value in (('developer_data_index', dev_data_index),
70
+ ('field_definition_number', field_def_num),
71
+ ('fit_base_type_id', base_type_id)):
72
+ if not isinstance(value, int):
73
+ raise FitParseError(
74
+ "Invalid %s=%r in developer field description" % (name, value))
75
+ if not isinstance(field_name, str) or not field_name:
76
+ field_name = "unnamed_dev_field_%s" % field_def_num
77
+ if not isinstance(units, str):
78
+ units = None
79
+ base_type = BASE_TYPES.get(base_type_id)
80
+ if base_type is None:
81
+ warnings.warn(
82
+ "Unknown base type %d for developer field %d; falling back to byte encoding."
83
+ % (base_type_id, field_def_num)
84
+ )
85
+ base_type = BASE_TYPE_BYTE
86
+
69
87
  if dev_data_index not in self.dev_types:
70
88
  if self.check_developer_data:
71
89
  raise FitParseError("No such dev_data_index=%s found" % (dev_data_index))
@@ -81,7 +99,7 @@ class DeveloperDataMixin:
81
99
  fields[field_def_num] = DevField(
82
100
  dev_data_index=dev_data_index,
83
101
  def_num=field_def_num,
84
- type=BASE_TYPES[base_type_id],
102
+ type=base_type,
85
103
  name=field_name,
86
104
  units=units,
87
105
  native_field_num=native_field_num
@@ -296,7 +314,8 @@ class FitFileDecoder(DeveloperDataMixin):
296
314
  if (field_size % base_type.size) != 0:
297
315
  warnings.warn(
298
316
  "Invalid field size %d for field '%s' of type '%s' (expected a multiple of %d); falling back to byte encoding." % (
299
- field_size, field.name, base_type.name, base_type.size)
317
+ field_size, field.name if field else 'unknown_%d' % field_def_num,
318
+ base_type.name, base_type.size)
300
319
  )
301
320
  base_type = BASE_TYPE_BYTE
302
321
 
@@ -470,7 +489,7 @@ class FitFileDecoder(DeveloperDataMixin):
470
489
  value = raw_value
471
490
 
472
491
  # Update compressed timestamp field
473
- if (field_def.def_num == FIELD_TYPE_TIMESTAMP.def_num) and (raw_value is not None):
492
+ if (field_def.def_num == FIELD_TYPE_TIMESTAMP.def_num) and isinstance(raw_value, int):
474
493
  self._compressed_ts_accumulator = raw_value
475
494
 
476
495
  field_datas.append(
@@ -69,12 +69,12 @@ class FitFileDataProcessor:
69
69
 
70
70
  def process_type_date_time(self, field_data):
71
71
  value = field_data.value
72
- if value is not None and value >= 0x10000000:
72
+ if isinstance(value, int) and value >= 0x10000000:
73
73
  field_data.value = datetime.datetime.fromtimestamp(timestamp=(UTC_REFERENCE + value), tz=datetime.timezone.utc).replace(tzinfo=None)
74
74
  field_data.units = None # Units were 's', set to None
75
75
 
76
76
  def process_type_local_date_time(self, field_data):
77
- if field_data.value is not None:
77
+ if isinstance(field_data.value, int):
78
78
  # NOTE: This value was created on the device using it's local timezone.
79
79
  # Unless we know that timezone, this value won't be correct. However, if we
80
80
  # assume UTC, at least it'll be consistent.
@@ -82,7 +82,7 @@ class FitFileDataProcessor:
82
82
  field_data.units = None
83
83
 
84
84
  def process_type_localtime_into_day(self, field_data):
85
- if field_data.value is not None:
85
+ if isinstance(field_data.value, int):
86
86
  # NOTE: Values larger or equal to 86400 should not be possible.
87
87
  # Additionally, if the value is exactly 86400, it will lead to an error when trying to
88
88
  # create the time with datetime.time(24, 0 , 0).
@@ -1,7 +1,7 @@
1
1
  # ***************** BEGIN AUTOMATICALLY GENERATED FIT PROFILE ******************
2
2
  # *************************** DO NOT EDIT THIS FILE ****************************
3
- # *********** EXPORTED PROFILE FROM SDK VERSION 21.218 ON 2026-10-06 ***********
4
- # ********* PARSED 216 TYPES (4761 VALUES), 125 MESSAGES (1572 FIELDS) *********
3
+ # *********** EXPORTED PROFILE FROM SDK VERSION 21.221 ON 2026-11-03 ***********
4
+ # ********* PARSED 220 TYPES (4819 VALUES), 128 MESSAGES (1599 FIELDS) *********
5
5
  # ***************************** APPLIED 2 PATCHES ******************************
6
6
 
7
7
  from fitparse.records import (
@@ -1871,6 +1871,8 @@ FIELD_TYPES = {
1871
1871
  34: 'segment', # Read/write/erase. Multiple Files. Directory=Segments
1872
1872
  35: 'segment_list', # Read/write/erase. Single File. Directory=Segments
1873
1873
  40: 'exd_configuration', # Read/write/erase. Single File. Directory=Settings
1874
+ 66: 'tsb_sonar_events', # Read/write/erase, multiple files. Directory=Events. Top-Side-Bouy (TSB).
1875
+ 67: 'tsb_diver_config', # Read/write/erase, single file. Top-Side-Bouy (TSB).
1874
1876
  0xF7: 'mfg_range_min', # 0xF7 - 0xFE reserved for manufacturer specific file types
1875
1877
  0xFE: 'mfg_range_max', # 0xF7 - 0xFE reserved for manufacturer specific file types
1876
1878
  },
@@ -2501,7 +2503,12 @@ FIELD_TYPES = {
2501
2503
  4954: 'fenix9_pro_51mm',
2502
2504
  4955: 'fenix9_pro_solar_47mm',
2503
2505
  4956: 'fenix9_pro_solar_51mm',
2506
+ 4962: 'edge_1060',
2507
+ 4970: 'edge_explore3',
2508
+ 4972: 'edge_explore3_plus',
2509
+ 5000: 'marq3',
2504
2510
  5019: 'cirqa_smart_band',
2511
+ 5026: 'descent_x10',
2505
2512
  5056: 'd2_mach2_pro',
2506
2513
  5133: 'fenix9_43mm',
2507
2514
  5134: 'fenix9', # 47mm & 57mm
@@ -3414,6 +3421,10 @@ FIELD_TYPES = {
3414
3421
  352: 'tymewear',
3415
3422
  353: 'exposure_lights',
3416
3423
  354: 'forto_cycling',
3424
+ 356: 'fair_weather_athletics',
3425
+ 357: 'hatch_factory',
3426
+ 358: 'torqano',
3427
+ 359: 'rockbros',
3417
3428
  5759: 'actigraphcorp',
3418
3429
  },
3419
3430
  ),
@@ -3566,6 +3577,9 @@ FIELD_TYPES = {
3566
3577
  319: 'tank_update',
3567
3578
  323: 'tank_summary',
3568
3579
  346: 'sleep_assessment',
3580
+ 364: 'tsb_sonar_message_event', # Message type for individual sonar event
3581
+ 365: 'tsb_settings', # Message type for storing Top-Side-Bouy (TSB) Settings
3582
+ 366: 'tsb_diver_config', # Message type for storing saved Top-Side-Bouy (TSB) Diver Configuration
3569
3583
  370: 'hrv_status_summary',
3570
3584
  371: 'hrv_value',
3571
3585
  372: 'raw_bbi',
@@ -4689,6 +4703,74 @@ FIELD_TYPES = {
4689
4703
  4: 'rem',
4690
4704
  },
4691
4705
  ),
4706
+ 'sonar_heading': FieldType(
4707
+ name='sonar_heading',
4708
+ base_type=BASE_TYPES[0x00], # enum
4709
+ values={
4710
+ 0: 'north',
4711
+ 1: 'north_east',
4712
+ 2: 'east',
4713
+ 3: 'south_east',
4714
+ 4: 'south',
4715
+ 5: 'south_west',
4716
+ 6: 'west',
4717
+ 7: 'north_west',
4718
+ },
4719
+ ),
4720
+ 'sonar_message_source': FieldType(
4721
+ name='sonar_message_source',
4722
+ base_type=BASE_TYPES[0x00], # enum
4723
+ values={
4724
+ 0: 'dive_app',
4725
+ 1: 'watch',
4726
+ },
4727
+ ),
4728
+ 'sonar_message_type': FieldType(
4729
+ name='sonar_message_type',
4730
+ base_type=BASE_TYPES[0x00], # enum
4731
+ values={
4732
+ 0: 'emergency_message',
4733
+ 1: 'canned_message',
4734
+ },
4735
+ ),
4736
+ 'sonar_packet_id': FieldType(
4737
+ name='sonar_packet_id',
4738
+ base_type=BASE_TYPES[0x00], # enum
4739
+ values={
4740
+ 0: 'xx_ant_id',
4741
+ 1: 't1_pressure_depth', # T1 tank pod messages [1-7]
4742
+ 2: 't1_msg_rly_from_tsb',
4743
+ 3: 'rsvd_3',
4744
+ 4: 't1_info_resp',
4745
+ 5: 't1_nav_rly_from_tsb',
4746
+ 6: 'rsvd_6',
4747
+ 7: 'rsvd_7',
4748
+ 8: 'rsvd_8', # T1 tank pod messages when watch Tx is available and for future expansion [8-15]
4749
+ 9: 'rsvd_9',
4750
+ 10: 't1_pressure_heart_resp_rate',
4751
+ 11: 'rsvd_11',
4752
+ 12: 't1_ack_relay',
4753
+ 13: 'rsvd_13',
4754
+ 14: 'rsvd_14',
4755
+ 15: 'rsvd_15',
4756
+ 16: 'tsb_emer_alert', # Top Side Box Messages [16-25]
4757
+ 17: 'tsb_canned_msg',
4758
+ 18: 'tsb_info_req',
4759
+ 19: 'tsb_nav',
4760
+ 21: 'rsvd_21',
4761
+ 22: 'tsb_ack',
4762
+ 23: 'rsvd_23',
4763
+ 24: 'rsvd_24',
4764
+ 25: 'rsvd_25',
4765
+ 26: 'wch_emer_alert', # Watch message [26-31]
4766
+ 27: 'wch_canned_msg',
4767
+ 28: 'wch_heart_resp_rate',
4768
+ 29: 'wch_ack',
4769
+ 30: 'rsvd_30',
4770
+ 31: 'rsvd_31',
4771
+ 32: 'gen_debug', # Used for logging general debug messages
4772
+ },
4773
+ ),
4692
4774
  'source_type': FieldType(
4693
4775
  name='source_type',
4694
4776
  base_type=BASE_TYPES[0x00], # enum
@@ -15543,6 +15625,167 @@ MESSAGE_TYPES = {
15543
15625
  ),
15544
15626
  },
15545
15627
  ),
15628
+ 364: MessageType( # Top-Side-Bouy (TSB) Sonar Event
15629
+ name='tsb_sonar_message_event',
15630
+ mesg_num=364,
15631
+ fields={
15632
+ 0: Field(
15633
+ name='sonar_packet_id',
15634
+ type=FIELD_TYPES['sonar_packet_id'],
15635
+ def_num=0,
15636
+ ),
15637
+ 21: Field( # Standard Deviation of Diver's Location
15638
+ name='diver_location_accuracy',
15639
+ type=BASE_TYPES[0x84], # uint16
15640
+ def_num=21,
15641
+ scale=1,
15642
+ units='degrees',
15643
+ ),
15644
+ 22: Field(
15645
+ name='message_type',
15646
+ type=FIELD_TYPES['sonar_message_type'],
15647
+ def_num=22,
15648
+ ),
15649
+ 23: Field(
15650
+ name='message_source',
15651
+ type=FIELD_TYPES['sonar_message_source'],
15652
+ def_num=23,
15653
+ ),
15654
+ },
15655
+ ),
15656
+ 365: MessageType( # Top-Side-Bouy (TSB) Settings
15657
+ name='tsb_settings',
15658
+ mesg_num=365,
15659
+ fields={
15660
+ 0: Field(
15661
+ name='tx_enable',
15662
+ type=FIELD_TYPES['bool'],
15663
+ def_num=0,
15664
+ ),
15665
+ 1: Field(
15666
+ name='proportional_gain',
15667
+ type=BASE_TYPES[0x88], # float32
15668
+ def_num=1,
15669
+ units='dB',
15670
+ ),
15671
+ 2: Field(
15672
+ name='integral_gain',
15673
+ type=BASE_TYPES[0x88], # float32
15674
+ def_num=2,
15675
+ units='dB',
15676
+ ),
15677
+ 3: Field(
15678
+ name='fixed_gain',
15679
+ type=BASE_TYPES[0x02], # uint8
15680
+ def_num=3,
15681
+ units='dB',
15682
+ ),
15683
+ 4: Field(
15684
+ name='rssi_setpoint',
15685
+ type=BASE_TYPES[0x86], # uint32
15686
+ def_num=4,
15687
+ ),
15688
+ 5: Field(
15689
+ name='plt_thresh_mult',
15690
+ type=BASE_TYPES[0x88], # float32
15691
+ def_num=5,
15692
+ ),
15693
+ 6: Field(
15694
+ name='plt_thresh',
15695
+ type=BASE_TYPES[0x86], # uint32
15696
+ def_num=6,
15697
+ ),
15698
+ 7: Field(
15699
+ name='transducer_spacing',
15700
+ type=BASE_TYPES[0x88], # float32
15701
+ def_num=7,
15702
+ units='m',
15703
+ ),
15704
+ 8: Field(
15705
+ name='calib_accel',
15706
+ type=BASE_TYPES[0x88], # float32
15707
+ def_num=8,
15708
+ ),
15709
+ 9: Field(
15710
+ name='calib_gyro',
15711
+ type=BASE_TYPES[0x88], # float32
15712
+ def_num=9,
15713
+ ),
15714
+ 10: Field(
15715
+ name='calib_mag',
15716
+ type=BASE_TYPES[0x88], # float32
15717
+ def_num=10,
15718
+ ),
15719
+ 11: Field(
15720
+ name='calib_mag_soft_x',
15721
+ type=BASE_TYPES[0x88], # float32
15722
+ def_num=11,
15723
+ ),
15724
+ 12: Field(
15725
+ name='calib_mag_soft_y',
15726
+ type=BASE_TYPES[0x88], # float32
15727
+ def_num=12,
15728
+ ),
15729
+ 13: Field(
15730
+ name='calib_mag_soft_z',
15731
+ type=BASE_TYPES[0x88], # float32
15732
+ def_num=13,
15733
+ ),
15734
+ 14: Field(
15735
+ name='max_tx_duty_cycle',
15736
+ type=BASE_TYPES[0x02], # uint8
15737
+ def_num=14,
15738
+ ),
15739
+ 15: Field(
15740
+ name='tx_chan',
15741
+ type=BASE_TYPES[0x02], # uint8
15742
+ def_num=15,
15743
+ ),
15744
+ 16: Field(
15745
+ name='search_gain',
15746
+ type=BASE_TYPES[0x02], # uint8
15747
+ def_num=16,
15748
+ units='dB',
15749
+ ),
15750
+ },
15751
+ ),
15752
+ 366: MessageType( # Top-Side-Bouy (TSB) Diver Configuration
15753
+ name='tsb_diver_config',
15754
+ mesg_num=366,
15755
+ fields={
15756
+ 0: Field(
15757
+ name='ant_id',
15758
+ type=BASE_TYPES[0x8C], # uint32z
15759
+ def_num=0,
15760
+ ),
15761
+ 1: Field(
15762
+ name='diver_name',
15763
+ type=BASE_TYPES[0x07], # string
15764
+ def_num=1,
15765
+ ),
15766
+ 2: Field(
15767
+ name='t1_tankpod_sw_ver',
15768
+ type=BASE_TYPES[0x84], # uint16
15769
+ def_num=2,
15770
+ scale=100,
15771
+ ),
15772
+ 3: Field(
15773
+ name='location_enabled',
15774
+ type=FIELD_TYPES['bool'],
15775
+ def_num=3,
15776
+ ),
15777
+ 4: Field(
15778
+ name='messaging_enabled',
15779
+ type=FIELD_TYPES['bool'],
15780
+ def_num=4,
15781
+ ),
15782
+ 5: Field( # ANT ID of the watch that claimed ownership of the diver's T1 (usually the diver's watch)
15783
+ name='t1_owner_ant_id',
15784
+ type=BASE_TYPES[0x8C], # uint32z
15785
+ def_num=5,
15786
+ ),
15787
+ },
15788
+ ),
15546
15789
  370: MessageType(
15547
15790
  name='hrv_status_summary',
15548
15791
  mesg_num=370,
@@ -316,6 +316,9 @@ class ComponentField(RecordBase):
316
316
  if self.bit_offset and self.bit_offset >= len(raw_value) << 3:
317
317
  raise ValueError()
318
318
 
319
+ if not all(isinstance(value, int) for value in raw_value):
320
+ return None
321
+
319
322
  unpacked_num = 0
320
323
 
321
324
  # Unpack byte array as little endian
@@ -324,11 +327,11 @@ class ComponentField(RecordBase):
324
327
 
325
328
  raw_value = unpacked_num
326
329
 
327
- # Mask and shift like a normal number
328
- if isinstance(raw_value, int):
329
- raw_value = (raw_value >> self.bit_offset) & ((1 << self.bits) - 1)
330
+ if not isinstance(raw_value, int):
331
+ return None
330
332
 
331
- return raw_value
333
+ # Mask and shift like a normal number
334
+ return (raw_value >> self.bit_offset) & ((1 << self.bits) - 1)
332
335
 
333
336
 
334
337
  class Crc:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "python-fitparse"
7
- version = "2.7.0"
7
+ version = "2.10.0"
8
8
  description = "Python library to parse ANT/Garmin .FIT files"
9
9
  authors = [
10
10
  {name = "David Cooper", email = "dave@kupesoft.com"},