From 9aab34aeda18a429fde950cbdb5b51a1b1d5a98e Mon Sep 17 00:00:00 2001 From: Fangrui Date: Tue, 24 Oct 2023 20:41:35 -0400 Subject: [PATCH 1/2] wrote garmin class --- dbdpy/garmin.py | 183 +++++++++++++++++++++++++++++++++++++++++++ dev-requirements.txt | 8 +- 2 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 dbdpy/garmin.py diff --git a/dbdpy/garmin.py b/dbdpy/garmin.py new file mode 100644 index 00000000..f745fa3a --- /dev/null +++ b/dbdpy/garmin.py @@ -0,0 +1,183 @@ +import pandas +from garmin_fit_sdk import Decoder, Stream +import datetime + + +class GarminDataProcessor: + FIT_EPOCH_S = 631065600 + + def __init__(self): + self.data = None + + def read_fit(self, file): + """ + Reads FIT file and stores the data in the class instance. + + Args: + file (str): The path to the FIT file to be read. + """ + stream = Stream.from_file(file) + decoder = Decoder(stream) + messages, errors = decoder.read( + apply_scale_and_offset=True, + convert_datetimes_to_dates=False, + convert_types_to_strings=True, + enable_crc_check=True, + expand_sub_fields=True, + expand_components=True, + merge_heart_rates=False, + mesg_listener=None + ) + print(errors) + self.data = messages + + def convert_time(self): + """ + Converts timestamps in the stored data to a human-readable format. + + Uses the FIT_EPOCH_S constant and adjusts for the time zone. + + Note: Requires data to be loaded using `read_fit` method first. + """ + time_delta = datetime.timedelta(hours=4) + if self.data is None: + return None + self.data['timestamp'] = [ + datetime.datetime.utcfromtimestamp( + self.FIT_EPOCH_S + self.data['timestamp'][i]) - time_delta + for i in range(len(self.data['timestamp'])) + ] + + def get_sleep_time(self): + """ + Extracts sleep start and wake times from the stored data. + + Note: Requires data to be loaded and timestamps to be converted using + `read_fit` and `convert_time` methods. + """ + if self.data is None: + return None + sleep = self.data # Replace with the actual sleep data frame + sleep_time, wake_time = None, None + for i in range(len(sleep['timestamp']) - 1): + time_difference = sleep['timestamp'][i + 1] - sleep['timestamp'][i] + if time_difference.seconds // 3600 > 1: + sleep_time = sleep['timestamp'][i] + wake_time = sleep['timestamp'][i + 1] + return sleep_time, wake_time + + def get_timestamp(self): + """ + Adjusts timestamps in the stored data, fixing inconsistencies and + converting them to a continuous sequence. + + Note: Requires data to be loaded using `read_fit` method first. + """ + if self.data is None: + return None + df = self.data # Replace with the actual data frame + for i in range(len(df) - 1): + if df.iloc[i, 0].astype(str) == 'nan': + timestamp_16 = df['timestamp_16'][i] + df.iloc[i, 0] = df.iloc[(i - 1), 0] + + for i in range(len(df)): + mesgTimestamp = int(df['timestamp'][i]) + try: + timestamp_16 = int(df['timestamp_16'][i]) + duration = (timestamp_16 - (mesgTimestamp & 0xFFFF)) & 0xFFFF + except ValueError: + continue + mesgTimestamp += duration + df.iloc[i, 0] = mesgTimestamp + + def get_floor(self): + """ + Calculates cumulative ascent and descent values over time, + returning a data frame. + + Note: Requires data to be loaded using `read_fit` method first. + """ + if self.data is None: + return None + df = self.data # Replace with the actual data frame + floor = df[['timestamp', 'ascent', 'descent']] + floor.iloc[0, 1:] = 0 + for i in range(len(floor) - 1): + if str(floor['ascent'][i + 1]) == 'nan': + floor.iloc[i + 1, 1] = floor.iloc[i, 1] + else: + floor.iloc[i + 1, 1] += floor.iloc[i, 1] + + if str(floor['descent'][i + 1]) == 'nan': + floor.iloc[i + 1, 2] = floor.iloc[i, 2] + else: + floor.iloc[i + 1, 2] += floor.iloc[i, 2] + return floor + + def get_distance(self): + """ + Calculates cumulative distance values over time, + returning a data frame. + + Note: Requires data to be loaded using `read_fit` method first. + """ + if self.data is None: + return None + df = self.data # Replace with the actual data frame + distance = df.iloc[:-5, :] + distance = distance[['timestamp', 'distance']] + distance.iloc[0, 1] = 0 + for i in range(len(distance) - 1): + i = i + 1 + if str(distance['distance'][i]) == 'nan': + if str(distance['distance'][i - 1]) != 'nan': + distance.iloc[i, 1] = distance['distance'][i - 1] + + if distance.iloc[i, 1] < distance['distance'][i - 1]: + distance.iloc[i, 1] = distance['distance'][i - 1] + return distance + + def get_hr(self): + """ + Extracts heart rate data from the stored data. + + Note: Requires data to be loaded using `read_fit` method first. + """ + if self.data is None: + return None + hr_df = self.data[~self.data['heart_rate'].isna()] + hr_df = hr_df.reset_index(drop=True) + hr_df = hr_df[['timestamp', 'heart_rate']] + return hr_df + + def get_intensity(self): + """ + Extracts intensity data from the stored data. + + Note: Requires data to be loaded using `read_fit` method first. + """ + if self.data is None: + return None + intensity = self.data[~self.data['intensity'].isna()] + intensity = intensity.reset_index(drop=True) + intensity = intensity[['timestamp', 'intensity']] + return intensity + + def get_calories(self): + """ + Calculates cumulative active calorie values over time, + returning a data frame. + + Note: Requires data to be loaded using `read_fit` method first. + """ + if self.data is None: + return None + calories = self.data.iloc[:-5, :] + calories = calories[['timestamp', 'active_calories']] + for i in range(len(calories) - 1): + if str((calories['active_calories'][i + 1]) == 'nan') or\ + (calories['active_calories'][i + 1] < + calories['active_calories'][i]): + calories.iloc[i + 1, 1] = calories.iloc[i, 1] + return calories diff --git a/dev-requirements.txt b/dev-requirements.txt index 3bca1471..6b6c139a 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,4 +1,10 @@ pytest pytest-pycodestyle pytest-black -black \ No newline at end of file +black + +datetime +pandas +numpy +matplotlib +garmin_fit_sdk \ No newline at end of file From fbb72dc94e97d955d77ac2c862b1e758c6282e6d Mon Sep 17 00:00:00 2001 From: Fangrui Date: Tue, 24 Oct 2023 20:42:12 -0400 Subject: [PATCH 2/2] changes occured when writting Garmin class --- dbdpy/__pycache__/__init__.cpython-39.pyc | Bin 138 -> 153 bytes dbdpy/__pycache__/device.cpython-39.pyc | Bin 1365 -> 1380 bytes tests/__pycache__/__init__.cpython-39.pyc | Bin 138 -> 153 bytes .../test_device.cpython-39-pytest-7.1.1.pyc | Bin 0 -> 1893 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/__pycache__/test_device.cpython-39-pytest-7.1.1.pyc diff --git a/dbdpy/__pycache__/__init__.cpython-39.pyc b/dbdpy/__pycache__/__init__.cpython-39.pyc index 8c2830fb2720c095121cf041b160ed44d5e1b0f5..3971875c254bff00acc20907fecfabae30b12985 100644 GIT binary patch delta 62 zcmeBToXN`i&Kk=^>gxzGK+H)ll5Kl%ky&b6H|)yT|&KG Q^*w|2U7TD3CdO+60Qr~^-T(jq delta 47 zcmbQq*u}`5$ji&c00d|GgeG#^Na*W_7N-^!>nCOAe*3Zc=$}G-JOxAbFFVD-#PfRJ+cM0`& S)%Oh6cX4tF*v!r7#R33itrH>u delta 49 zcmaFDb(M=dk(ZZ?0SNvH3QgpGA)%`uTAW%`te=#blarj0ny2rQTAW>yU$9w<(TfED DWjPNX diff --git a/tests/__pycache__/__init__.cpython-39.pyc b/tests/__pycache__/__init__.cpython-39.pyc index 935c7b2990c6ab78f320a2b83d7959f36b363bab..d8ec775929bcf85771ba61b26ebef7fac483970f 100644 GIT binary patch delta 62 zcmeBToXN`i&Kk=^>gxzGK+H)ll5Kl%ky&b6H|)yT|&KG Q^*w|2U7TD3CdO+60Qr~^-T(jq delta 47 zcmbQq*u}`5$ji&c00d|GgeG#^Na*W_7N-^!>nCOA976u@Wny}SNM($q<43Zk?Sf$O#7)E`inxN!`n)P%SpMHGa*GwUdkR?Cd+ zbhi?eI`nVoArx;<4fMb0rRN^!+LQl*9&#vsZ?xVnhD0!L-j_zBH}iY(>}-?3v;55l z|HCBYZ#1SK8;rX!vqA&B+iTr4FQ zgGvN$+Lbe zxSD}*ba0fUMbeYXNpr}M>Ut!fS5R}?dSgl6yDvm8 z)H_i(7g=0H{JVY{_u>K~fh+Kesuq5x{*jewV>O(LtW8HgsU!808=>B3ZLBQ6BH~?G z_4Ri95tK%@(_ADni#zR|{CO|UVkG{O${$jtq^ZCw$HalSbK^ve^f=8g5xyAPAkI;u_3D|3Jh&w&=%)e9=#2#NrH8aKO*j^u z*uWCk!8pUy+D(3dDX>CkHz^L1Lfp{Khmh9M9|IE~B)$PdGv&Zq$W#ez15-c@FlCo^ zNlW9zsGQQ#Ou3~i|AG5%F{5;WDHHvgDeD4L)`Th0fGJC}WCKI?B(nsci=2s{bQCnm zw5Ry|MP~qk;y0ZE+JsLaj=7Vy{?wVksQ61~f(Dt;V_rHyDe%ggrKgE)jEQYb5o-gj zPUUJ^TYI6m-5Y$BGoA`48UYTw2;JH_bfCPQL%7_Xqcko!Tx> zO=aRz?%1dQS~>MA%GLzyo~LnCRu+qAss$|!no=Q>A=h7>+B7x@p?B_mn&s>$<)0yz T5-axsoiV*D4mGIrui^a%A3_Od literal 0 HcmV?d00001