Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
eb0cd85
Ignore default output directory, vim temp files.
Jan 16, 2016
187d3d1
Handy Makefile
aaronferrucci Dec 31, 2016
4037bb0
Print url before downloading.
aaronferrucci Dec 31, 2016
4b687b8
Fix typo
aaronferrucci Nov 23, 2017
11abe1c
Add vimdiff target for easy merging.
aaronferrucci Dec 5, 2017
f017d90
Don't end csv records with comma
aaronferrucci Jan 1, 2018
ae6f985
Swap order for vimdiff, so the to-be-modified file has the selection.
aaronferrucci Jan 3, 2018
0588a64
Now writing per-activity json files.
aaronferrucci Mar 27, 2018
e312cdc
Some progress on extracting data from the new format.
aaronferrucci Mar 28, 2018
b7f1969
Some progress on refactoring dict lookup.
aaronferrucci Mar 29, 2018
26964cc
Completed field fill-in, not necessarily correctly.
aaronferrucci Mar 30, 2018
69e3ab9
Accomodate new garmin data format.
aaronferrucci Mar 31, 2018
f769303
Remove unused format, unzip args. Add debug, quiet args.
aaronferrucci Apr 2, 2018
3a33cb1
Update the 'post-auth' url
aaronferrucci Apr 16, 2018
5d81ae2
Hey, gvimdiff.
aaronferrucci May 22, 2018
93e7e7f
Add DEBUG flag
aaronferrucci Aug 27, 2018
1300fd1
Accomodate the latest changes at garmin. Look up activity type, event…
aaronferrucci Aug 27, 2018
8ce1c49
Push device info stuff into a class.
aaronferrucci Aug 27, 2018
86482b2
Remove some unused imports; fix deplorable indents.
aaronferrucci Aug 28, 2018
4f14372
A bit of comment cleanup; remove unneeded debugging output.
aaronferrucci Aug 28, 2018
6b35a4d
Linewrap tidying; remove broken 'download_all' feature
aaronferrucci Aug 28, 2018
cf51147
Properties capture into a class
aaronferrucci Aug 28, 2018
850922c
Remove trailing whitespace
aaronferrucci Aug 28, 2018
0667755
Comment for Properties
aaronferrucci Sep 3, 2018
7034390
Fix the latest garmin breakage: user-agent string needed to be updated.
aaronferrucci Oct 20, 2018
f732b2b
Ignore .pyc
aaronferrucci May 6, 2019
e9abd9b
Following the login method of tapiriik; breaking code out into separa…
aaronferrucci May 6, 2019
a08a45e
Checkpoint: moving forward with the port.
aaronferrucci May 7, 2019
74a6633
Progress: json dump of some activities looks sane.
aaronferrucci May 7, 2019
16a9e4a
cmd line args, less debug output
aaronferrucci May 8, 2019
1941499
Call new script from make
aaronferrucci May 8, 2019
7705f06
Approaching mvp milestone
aaronferrucci May 8, 2019
1a6b645
Check for overlap before adding new data
aaronferrucci Jan 26, 2020
38dde7d
a bit of http error checking
aaronferrucci Mar 14, 2021
0a52d61
Copy a magic header item from https://github.com/moderation/garmin-co…
aaronferrucci Mar 14, 2021
eb63b2e
parens around print arguments
aaronferrucci May 10, 2021
bd968d3
parens around print args
aaronferrucci May 10, 2021
5e7397d
Fix login error by supplying User-Agent. Convert unimplemented APIExc…
aaronferrucci May 10, 2021
f97df86
data shows up as 'bytes' object, instead of string. to analyze, but t…
aaronferrucci May 10, 2021
8f5b963
Properties are stored both as bytes (as the data is delivered, now) a…
aaronferrucci May 16, 2021
4c15e32
specify python3
aaronferrucci Jun 2, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@

[0-9\-]*_garmin_connect_export
.*.swp
.DS_Store
extras/
*.pyc
30 changes: 30 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
SHELL := /bin/bash
COUNT := 4
# DEBUG = --debug
DEBUG =
.PHONY: help
help:
@echo Usage:
@echo make go COUNT=\<number of activities to download\>

.PHONY: verify_overlap
# the new activity file should overlap the old one (otherwise vimdiff
# sometimes seems confused).
verify_overlap:
@grep -q $(shell tail -1 $(shell find . -name activities.csv) | cut --delimiter=, --fields=1) ../garmin_running/activities.csv

.PHONY: go
go:
./gcdownload.py --username aaronferrucci --count $(COUNT) $(DEBUG)

NUM_ACTIVITIES = $(shell find . -name activities.csv | wc -l)
.PHONY: count_activities_csv
count_activities_csv:
@if [ $(NUM_ACTIVITIES) -ne 1 ] ; then \
echo "Too many activities.csv files found ($(NUM_ACTIVITIES))"; \
false; \
fi

.PHONY: vimdiff
vimdiff: verify_overlap
gvimdiff ../garmin_running/activities.csv $(shell find . -name activities.csv)
53 changes: 53 additions & 0 deletions cmdlineargs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import argparse
from datetime import datetime
def get_args():
current_date = datetime.now().strftime('%Y-%m-%d')
activities_directory = './' + current_date + '_garmin_connect_export'
parser = argparse.ArgumentParser()

parser.add_argument(
'--quiet',
help="stifle all output",
action="store_true"
)
parser.add_argument(
'--debug',
help="lots of console output",
action="store_true"
)
parser.add_argument(
'--version',
help="print version and exit",
action="store_true"
)
parser.add_argument(
'--username',
help="your Garmin Connect username (otherwise, you will be prompted)",
nargs='?'
)
parser.add_argument(
'--password',
help="your Garmin Connect password (otherwise, you will be prompted)",
nargs='?'
)

parser.add_argument(
'-c',
'--count',
nargs='?',
default="1",
help="number of recent activities to download (default: 1)"
)

parser.add_argument(
'-d',
'--directory',
nargs='?',
default=activities_directory,
help="save directory (default: './YYYY-MM-DD_garmin_connect_export')"
)

args = parser.parse_args()
return args


55 changes: 55 additions & 0 deletions deviceinfo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import json
class DeviceInfo():
devices_url = "https://connect.garmin.com/modern/proxy/device-service/deviceregistration/devices"
keys = [
'currentFirmwareVersion',
'displayName',
'partNumber',
'serialNumber',
]

def __init__(self, session):
self.session = session
http_data = session.get(DeviceInfo.devices_url, allow_redirects=False)
if http_data.status_code != 200:
print("DeviceInfo error code: %d" % (http_data.status_code))
self.devices = None
return

devices = json.loads(http_data.text)
self.device_info = {}
for dev in devices:
dev_id = dev['deviceId']
this_device = {}
for key in DeviceInfo.keys:
this_device[key] = dev.get(key, None)
self.device_info[dev_id] = this_device

# backward compatibility hack: prepend ' ', append ".0.0"
# to firmware version.
for dev_id in self.device_info:
fw = self.device_info[dev_id]['currentFirmwareVersion']
fw = ' ' + fw + ".0.0"
self.device_info[dev_id]['currentFirmwareVersion'] = fw

def do_print(self):
for dev_id in self.device_info:
print(dev_id)
for dev_parameter in self.device_info[dev_id]:
print(" " + dev_parameter + ": " + self.device_info[dev_id][dev_parameter])

def displayName(self, deviceId):
try:
device = self.device_info[deviceId]['displayName']
except KeyError:
device = ""

try:
version = self.device_info[deviceId]['currentFirmwareVersion']
except KeyError:
version = ""

displayName = device + ' ' + version
return displayName


126 changes: 126 additions & 0 deletions gcdownload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/usr/bin/python3
import json

from gclogin import GarminLogin
from properties import Properties
from deviceinfo import DeviceInfo
from typeinfo import TypeInfo
from cmdlineargs import get_args
from utils import csvFormat, dictFind, activity_to_csv
from sys import argv
from os.path import isdir
from os.path import isfile
from os import mkdir
from getpass import getpass

script_version = '1.4.0'
args = get_args()
if args.version:
print(argv[0] + ", version " + script_version)
exit(0)

# utilities - put these somewhere else?

gcl = GarminLogin()
username = args.username if args.username else raw_input('Username: ')
password = args.password if args.password else getpass()
session = gcl._get_session(email=username, password=password)

devInfo = DeviceInfo(session)
# print("\nDevices:")
# devInfo.do_print()

# activity properties
activity_properties_url = 'https://connect.garmin.com/modern/main/js/properties/activity_types/activity_types.properties?bust=4.10.1.0'
activity_properties = Properties(session, activity_properties_url, b'activity_type_')
# print("\nActivity Properties:")
# activity_properties.do_print()

activity_type_url = "https://connect.garmin.com/modern/proxy/activity-service/activity/activityTypes"
activity_type_info = TypeInfo(session, activity_type_url, activity_properties)
# print("\nActivity Type Info:")
# activity_type_info.do_print()

event_properties_url = 'https://connect.garmin.com/modern/main/js/properties/event_types/event_types.properties?bust=4.10.1.0'
event_properties = Properties(session, event_properties_url)
# print("\nEvent Properties:")
# event_properties.do_print()

event_type_url = 'https://connect.garmin.com/modern/proxy/activity-service/activity/eventTypes'
event_type_info = TypeInfo(session, event_type_url, event_properties)
# print("\nEvent Type Info:")
# event_type_info.do_print()

if not isdir(args.directory):
mkdir(args.directory)


csv_filename = args.directory + '/activities.csv'
csv_existed = isfile(csv_filename)

csv_file = open(csv_filename, 'a')

# Write header to CSV file
if not csv_existed:
csv_file.write('Activity ID,Activity Name,Description,Begin Timestamp,Begin Timestamp (Raw Milliseconds),End Timestamp,End Timestamp (Raw Milliseconds),Device,Activity Parent,Activity Type,Event Type,Activity Time Zone,Max. Elevation,Max. Elevation (Raw),Begin Latitude (Decimal Degrees Raw),Begin Longitude (Decimal Degrees Raw),End Latitude (Decimal Degrees Raw),End Longitude (Decimal Degrees Raw),Average Moving Speed,Average Moving Speed (Raw),Max. Heart Rate (bpm),Average Heart Rate (bpm),Max. Speed,Max. Speed (Raw),Calories,Calories (Raw),Duration (h:m:s),Duration (Raw Seconds),Moving Duration (h:m:s),Moving Duration (Raw Seconds),Average Speed,Average Speed (Raw),Distance,Distance (Raw),Max. Heart Rate (bpm),Min. Elevation,Min. Elevation (Raw),Elevation Gain,Elevation Gain (Raw),Elevation Loss,Elevation Loss (Raw)\n')

# start of experimental get-activities code
total_to_download = int(args.count)
total_downloaded = 0
url_gc_search = 'http://connect.garmin.com/modern/proxy/activitylist-service/activities/search/activities?'
url_gc_modern_activity = 'https://connect.garmin.com/modern/proxy/activity-service/activity/'
while total_downloaded < total_to_download:
# Maximum of 100... 400 return status if over 100. So download 100 or whatever remains if less than 100.
if total_to_download - total_downloaded > 100:
num_to_download = 100
else:
num_to_download = total_to_download - total_downloaded

search_params = {'start': total_downloaded, 'limit': num_to_download}
http_data = session.get(url_gc_search, params=search_params)
if http_data.status_code != 200:
print("Activity load error code: %d" % (http_data.status_code))

activities = json.loads(http_data.text)

# print "### activities:"
# print json.dumps(activities, indent=4, sort_keys=True)
# print "###"

for a in activities:
activityId = str(a['activityId'])

if not args.quiet:
print('activity: [' + activityId + ']')
print(a['activityName'])
modern_activity_url = url_gc_modern_activity + activityId
if args.debug:
print("url: " + modern_activity_url)

result = session.get(modern_activity_url)
results = json.loads(result.text)

activity_filename = args.directory + '/' + activityId + '.json'
if args.debug:
print("filename: " + activity_filename)

save_file = open(activity_filename, 'w')
save_file.write(json.dumps(results, indent=4, sort_keys=True))
save_file.close()

# Write stats to CSV.
csv_record = activity_to_csv(results, a, devInfo, activity_type_info, event_type_info)
if args.debug:
print("data: " + csv_record)

# csv_file.write(csv_record.encode('utf8'))
csv_file.write(csv_record)

total_downloaded += num_to_download
# End while loop for multiple chunks.

csv_file.close()

if not args.quiet:
print('Done!')

Loading