|
3 | 3 | import glob |
4 | 4 | import os |
5 | 5 | from datetime import timezone |
| 6 | +from typing import Any |
6 | 7 | from zoneinfo import ZoneInfo |
7 | 8 |
|
8 | 9 | import fitparse |
9 | 10 |
|
10 | | -allowed_fields = [ |
| 11 | +FIELDS_ALLOWED = [ |
11 | 12 | "timestamp", |
12 | 13 | "position_lat", |
13 | 14 | "position_long", |
|
21 | 22 | "cadence", |
22 | 23 | "fractional_cadence", |
23 | 24 | ] |
24 | | -required_fields = ["timestamp", "position_lat", "position_long"] |
| 25 | +FIELDS_REQUIRED = ["timestamp", "position_lat", "position_long"] |
25 | 26 |
|
26 | 27 | UTC = timezone.utc |
27 | 28 | TZ = ZoneInfo("US/Central") |
28 | 29 |
|
29 | 30 |
|
30 | | -def write_fitfile_to_csv(fitfile, output_path: str, tz: ZoneInfo = TZ): |
31 | | - messages = fitfile.messages |
| 31 | +def write_to_csv(data: list[dict[str, Any]], output_path: str) -> None: |
| 32 | + # write to csv |
| 33 | + with open(output_path, "w") as f: |
| 34 | + writer = csv.writer(f) |
| 35 | + writer.writerow(FIELDS_ALLOWED) |
| 36 | + for entry in data: |
| 37 | + writer.writerow([str(entry.get(k, "")) for k in FIELDS_ALLOWED]) |
| 38 | + print("wrote %s" % output_path) |
| 39 | + |
| 40 | + |
| 41 | +def collect_data(filepath: str, tz: ZoneInfo = TZ) -> list[dict[str, Any]]: |
| 42 | + # Parse the .fit file |
| 43 | + fitfile = fitparse.FitFile( |
| 44 | + filepath, data_processor=fitparse.StandardUnitsDataProcessor() |
| 45 | + ) |
| 46 | + |
32 | 47 | data = [] |
| 48 | + messages = fitfile.messages |
| 49 | + |
33 | 50 | for m in messages: |
34 | 51 | skip = False |
35 | 52 | if not hasattr(m, "fields"): |
36 | 53 | continue |
37 | 54 | fields = m.fields |
38 | | - # check for important data types |
| 55 | + |
| 56 | + # check for desired data and collect it |
39 | 57 | mdata = {} |
40 | 58 | for field in fields: |
41 | | - if field.name in allowed_fields: |
| 59 | + if field.name in FIELDS_ALLOWED: |
42 | 60 | if field.name == "timestamp": |
43 | | - ts_value = field.value |
44 | | - if ts_value.tzinfo is None: |
45 | | - ts_value = ts_value.replace(tzinfo=UTC) |
46 | | - mdata[field.name] = ts_value.astimezone(tz) |
| 61 | + timestamp_value = field.value |
| 62 | + if timestamp_value.tzinfo is None: |
| 63 | + timestamp_value = timestamp_value.replace(tzinfo=UTC) |
| 64 | + mdata[field.name] = timestamp_value.astimezone(tz) |
47 | 65 | else: |
48 | 66 | mdata[field.name] = field.value |
49 | | - for rf in required_fields: |
50 | | - if rf not in mdata: |
| 67 | + |
| 68 | + for required_field in FIELDS_REQUIRED: |
| 69 | + if required_field not in mdata: |
51 | 70 | skip = True |
| 71 | + |
52 | 72 | if not skip: |
53 | 73 | data.append(mdata) |
54 | | - # write to csv |
55 | | - with open(output_path, "w") as f: |
56 | | - writer = csv.writer(f) |
57 | | - writer.writerow(allowed_fields) |
58 | | - for entry in data: |
59 | | - writer.writerow([str(entry.get(k, "")) for k in allowed_fields]) |
60 | | - print("wrote %s" % output_path) |
| 74 | + |
| 75 | + return data |
61 | 76 |
|
62 | 77 |
|
63 | 78 | def parse_args() -> argparse.Namespace: |
@@ -86,19 +101,20 @@ def parse_args() -> argparse.Namespace: |
86 | 101 | def main(): |
87 | 102 | args = parse_args() |
88 | 103 |
|
| 104 | + # Identify .fit files |
89 | 105 | fit_files = glob.glob(args.dir + "/*.fit") |
| 106 | + |
90 | 107 | for file in fit_files: |
| 108 | + # Use the same filename, just change extension to .csv |
91 | 109 | base_filename = file.removesuffix(".fit") |
92 | 110 | new_filename = base_filename + ".csv" |
93 | 111 | if not args.overwrite and os.path.exists(new_filename): |
94 | | - # print('%s already exists. skipping.' % new_filename) |
95 | 112 | continue |
96 | | - fitfile = fitparse.FitFile( |
97 | | - file, data_processor=fitparse.StandardUnitsDataProcessor() |
98 | | - ) |
99 | 113 |
|
100 | 114 | print("converting %s" % file) |
101 | | - write_fitfile_to_csv(fitfile, new_filename, tz=ZoneInfo(args.timezone)) |
| 115 | + data = collect_data(file, tz=ZoneInfo(args.timezone)) |
| 116 | + write_to_csv(data, new_filename) |
| 117 | + |
102 | 118 | print("finished conversions") |
103 | 119 |
|
104 | 120 |
|
|
0 commit comments