I've used a Smart Scale since 2020 with its own app which eventually became Zepp Life.
Having acquired a Garmin a while ago, would be a pity to lose all that data.
The good news is that we can pull data from other accounts or activity trackers into Garmin Connect in the import page
https://connect.garmin.com/modern/import-data
So, I exported the Body data as CSV from Zepp Life.
The data looks like this
time,weight,height,bmi,fatRate,bodyWaterRate,boneMass,metabolism,muscleRate,visceralFat
2020-09-01 08:00:17+0000,83.6,186.0,24.1,0.0,0.0,0.0,0.0,0.0,0.0
2020-09-01 22:02:16+0000,82.8,186.0,23.9,0.0,0.0,0.0,0.0,0.0,0.0
After trying to import, realized that Garmin doesn't support uploading that file.
The question emerging is then which format does the CSV need have to be able to upload it in Garmin?
Thanks to someone in Reddit, found that it's something like this
Body
weight,bmi,fat,date,time
83.6,24.1,0.0,2020-09-01,08:00:17
82.8,23.9,0.0,2020-09-01,22:02:16
Eventually also found here as well.
So, initially created a simple Python script that reads weight and body composition data from a "zep_life.csv" file, and converted it into a CSV format that was ready to be imported into Garmin
This time I wasn't getting a warning anymore but it got stuck
as in, nothing seemed to have happend.
I figured it had to do with the size of the file.
So, instead of generating only one file, I generated one file per year and it worked
This is the used script
import csv
import datetime
INPUT_CSV = "zep_life.csv"
def zep_to_garmin_csv():
"""
Reads a CSV file named "zep_life.csv" and writes multiple yearly CSVs named
"garmin_ready_<year>.csv". Each output file has the first line "Body",
followed by "weight,bmi,fat,date,time" as headers. Rows are based on the
'time' field in the input, split by year.
Usage:
1. Place "zep_life.csv" in the same directory as this script.
2. Run this script.
3. For each year encountered in "zep_life.csv", a file named
"garmin_ready_<year>.csv" will be created.
Requirements:
- The input CSV must have the columns: "time", "weight", "bmi", "fatRate".
- "time" must be in a format parseable by datetime.strptime with
"%Y-%m-%d %H:%M:%S%z".
- "fatRate" of "null" or empty is replaced with "0.0".
"""
yearly_writers = {}
with open(INPUT_CSV, mode="r", encoding="utf-8-sig") as fin:
reader = csv.DictReader(fin)
for row in reader:
raw_time = row.get("time", "").strip()
weight_str = row.get("weight", "").strip()
bmi_str = row.get("bmi", "").strip()
fat_str = row.get("fatRate", "").strip()
if not raw_time or not weight_str:
continue
try:
dt = datetime.datetime.strptime(raw_time, "%Y-%m-%d %H:%M:%S%z")
except ValueError:
continue
date_str = dt.strftime("%Y-%m-%d") # "YYYY-MM-DD"
time_str = dt.strftime("%H:%M:%S") # "HH:MM:SS"
if fat_str.lower() in ("null", ""):
fat_str = "0.0"
# If we haven't opened a file for this year yet, do so and write headers.
year = dt.year
if year not in yearly_writers:
fout = open(f"garmin_ready_{year}.csv", "w", newline="", encoding="utf-8")
writer = csv.writer(fout)
# First line: "Body"
writer.writerow(["Body"])
# Second line: headers
writer.writerow(["weight", "bmi", "fat", "date", "time"])
# Store it in the dictionary
yearly_writers[year] = (fout, writer)
# Write the row to the proper year's CSV.
_, csv_writer = yearly_writers[year]
csv_writer.writerow([weight_str, bmi_str, fat_str, date_str, time_str])
# Close all the files
for fout, _ in yearly_writers.values():
fout.close()
print("Done! Split files by year.")
if __name__ == "__main__":
zep_to_garmin_csv()