ruby_garmin_connect 0.1.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +21 -0
- data/LICENSE +21 -0
- data/README.md +295 -0
- data/lib/garmin_connect/api/activities.rb +190 -0
- data/lib/garmin_connect/api/badges.rb +94 -0
- data/lib/garmin_connect/api/body_composition.rb +127 -0
- data/lib/garmin_connect/api/devices.rb +118 -0
- data/lib/garmin_connect/api/health.rb +139 -0
- data/lib/garmin_connect/api/metrics.rb +177 -0
- data/lib/garmin_connect/api/user.rb +39 -0
- data/lib/garmin_connect/api/wellness.rb +45 -0
- data/lib/garmin_connect/api/workouts.rb +62 -0
- data/lib/garmin_connect/auth/oauth1_token.rb +43 -0
- data/lib/garmin_connect/auth/oauth2_token.rb +70 -0
- data/lib/garmin_connect/auth/sso.rb +303 -0
- data/lib/garmin_connect/auth/token_store.rb +79 -0
- data/lib/garmin_connect/client.rb +213 -0
- data/lib/garmin_connect/connection.rb +148 -0
- data/lib/garmin_connect/errors.rb +46 -0
- data/lib/garmin_connect/version.rb +5 -0
- data/lib/garmin_connect.rb +35 -0
- metadata +136 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GarminConnect
|
|
4
|
+
module API
|
|
5
|
+
# Body composition, weight, hydration, and blood pressure endpoints.
|
|
6
|
+
module BodyComposition
|
|
7
|
+
# Get body composition data for a date range.
|
|
8
|
+
# @param start_date [Date, String]
|
|
9
|
+
# @param end_date [Date, String]
|
|
10
|
+
def body_composition(start_date = today, end_date = start_date)
|
|
11
|
+
connection.get(
|
|
12
|
+
"/weight-service/weight/dateRange",
|
|
13
|
+
params: {
|
|
14
|
+
"startDate" => format_date(start_date),
|
|
15
|
+
"endDate" => format_date(end_date)
|
|
16
|
+
}
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Get weigh-ins for a date range.
|
|
21
|
+
# @param start_date [Date, String]
|
|
22
|
+
# @param end_date [Date, String]
|
|
23
|
+
def weigh_ins(start_date, end_date)
|
|
24
|
+
connection.get(
|
|
25
|
+
"/weight-service/weight/range/#{format_date(start_date)}/#{format_date(end_date)}",
|
|
26
|
+
params: { "includeAll" => true }
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Get weigh-ins for a specific day.
|
|
31
|
+
# @param date [Date, String]
|
|
32
|
+
def daily_weigh_ins(date = today)
|
|
33
|
+
connection.get(
|
|
34
|
+
"/weight-service/weight/dayview/#{format_date(date)}",
|
|
35
|
+
params: { "includeAll" => true }
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Add a weigh-in.
|
|
40
|
+
# @param value [Float] weight value
|
|
41
|
+
# @param date [Date, String] date of the weigh-in
|
|
42
|
+
# @param unit_key [String] "kg" or "lbs"
|
|
43
|
+
def add_weigh_in(value, date: today, unit_key: "kg")
|
|
44
|
+
timestamp = "#{format_date(date)}T12:00:00.000"
|
|
45
|
+
connection.post(
|
|
46
|
+
"/weight-service/user-weight",
|
|
47
|
+
body: {
|
|
48
|
+
"dateTimestamp" => timestamp,
|
|
49
|
+
"gmtTimestamp" => timestamp,
|
|
50
|
+
"unitKey" => unit_key,
|
|
51
|
+
"sourceType" => "MANUAL",
|
|
52
|
+
"value" => value
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Delete a specific weigh-in.
|
|
58
|
+
# @param date [Date, String]
|
|
59
|
+
# @param weight_pk [String, Integer] the weigh-in version/pk
|
|
60
|
+
def delete_weigh_in(date, weight_pk)
|
|
61
|
+
connection.delete("/weight-service/weight/#{format_date(date)}/byversion/#{weight_pk}")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# --- Hydration ---
|
|
65
|
+
|
|
66
|
+
# Get daily hydration data.
|
|
67
|
+
# @param date [Date, String]
|
|
68
|
+
def hydration(date = today)
|
|
69
|
+
connection.get("/usersummary-service/usersummary/hydration/daily/#{format_date(date)}")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Log hydration intake.
|
|
73
|
+
# @param value_in_ml [Float] amount in milliliters (negative to subtract)
|
|
74
|
+
# @param date [Date, String]
|
|
75
|
+
def log_hydration(value_in_ml, date: today)
|
|
76
|
+
connection.put(
|
|
77
|
+
"/usersummary-service/usersummary/hydration/log",
|
|
78
|
+
body: {
|
|
79
|
+
"calendarDate" => format_date(date),
|
|
80
|
+
"timestampLocal" => "#{format_date(date)}T#{Time.now.strftime("%H:%M:%S")}.000",
|
|
81
|
+
"valueInML" => value_in_ml
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# --- Blood Pressure ---
|
|
87
|
+
|
|
88
|
+
# Get blood pressure data for a date range.
|
|
89
|
+
# @param start_date [Date, String]
|
|
90
|
+
# @param end_date [Date, String]
|
|
91
|
+
def blood_pressure(start_date, end_date)
|
|
92
|
+
connection.get(
|
|
93
|
+
"/bloodpressure-service/bloodpressure/range/#{format_date(start_date)}/#{format_date(end_date)}",
|
|
94
|
+
params: { "includeAll" => true }
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Log a blood pressure measurement.
|
|
99
|
+
# @param systolic [Integer]
|
|
100
|
+
# @param diastolic [Integer]
|
|
101
|
+
# @param pulse [Integer]
|
|
102
|
+
# @param notes [String, nil]
|
|
103
|
+
# @param date [Date, String]
|
|
104
|
+
def log_blood_pressure(systolic:, diastolic:, pulse:, notes: nil, date: today)
|
|
105
|
+
timestamp = "#{format_date(date)}T#{Time.now.strftime("%H:%M:%S")}.000"
|
|
106
|
+
body = {
|
|
107
|
+
"measurementTimestampLocal" => timestamp,
|
|
108
|
+
"measurementTimestampGMT" => timestamp,
|
|
109
|
+
"systolic" => systolic,
|
|
110
|
+
"diastolic" => diastolic,
|
|
111
|
+
"pulse" => pulse,
|
|
112
|
+
"sourceType" => "MANUAL"
|
|
113
|
+
}
|
|
114
|
+
body["notes"] = notes if notes
|
|
115
|
+
|
|
116
|
+
connection.post("/bloodpressure-service/bloodpressure", body: body)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Delete a blood pressure measurement.
|
|
120
|
+
# @param date [Date, String]
|
|
121
|
+
# @param version [String, Integer]
|
|
122
|
+
def delete_blood_pressure(date, version)
|
|
123
|
+
connection.delete("/bloodpressure-service/bloodpressure/#{format_date(date)}/#{version}")
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GarminConnect
|
|
4
|
+
module API
|
|
5
|
+
# Device and gear management endpoints.
|
|
6
|
+
module Devices
|
|
7
|
+
# List all registered devices.
|
|
8
|
+
def devices
|
|
9
|
+
connection.get("/device-service/deviceregistration/devices")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Get settings for a specific device (includes alarms).
|
|
13
|
+
# @param device_id [String, Integer]
|
|
14
|
+
def device_settings(device_id)
|
|
15
|
+
connection.get("/device-service/deviceservice/device-info/settings/#{device_id}")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Get the last used device.
|
|
19
|
+
def last_used_device
|
|
20
|
+
connection.get("/device-service/deviceservice/mylastused")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Get primary training device info.
|
|
24
|
+
def primary_training_device
|
|
25
|
+
connection.get("/web-gateway/device-info/primary-training-device")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Get solar charging data for a device.
|
|
29
|
+
# @param device_id [String, Integer]
|
|
30
|
+
# @param start_date [Date, String]
|
|
31
|
+
# @param end_date [Date, String]
|
|
32
|
+
# @param single_day [Boolean]
|
|
33
|
+
def device_solar_data(device_id, start_date, end_date, single_day: false)
|
|
34
|
+
connection.get(
|
|
35
|
+
"/web-gateway/solar/#{device_id}/#{format_date(start_date)}/#{format_date(end_date)}",
|
|
36
|
+
params: { "singleDayView" => single_day }
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Get alarms for all devices.
|
|
41
|
+
def device_alarms
|
|
42
|
+
all_devices = devices
|
|
43
|
+
return [] unless all_devices.is_a?(Array)
|
|
44
|
+
|
|
45
|
+
all_devices.filter_map do |device|
|
|
46
|
+
device_id = device["deviceId"]
|
|
47
|
+
next unless device_id
|
|
48
|
+
|
|
49
|
+
settings = device_settings(device_id)
|
|
50
|
+
alarms = settings&.dig("alarms")
|
|
51
|
+
{ "device" => device, "alarms" => alarms } if alarms
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# --- Gear ---
|
|
56
|
+
|
|
57
|
+
# Get all gear for the current user.
|
|
58
|
+
def gear
|
|
59
|
+
connection.get(
|
|
60
|
+
"/gear-service/gear/filterGear",
|
|
61
|
+
params: { "userProfilePk" => user_profile_pk }
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Get gear linked to an activity.
|
|
66
|
+
# @param activity_id [String, Integer]
|
|
67
|
+
def activity_gear(activity_id)
|
|
68
|
+
connection.get(
|
|
69
|
+
"/gear-service/gear/filterGear",
|
|
70
|
+
params: { "activityId" => activity_id }
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Get statistics for a gear item.
|
|
75
|
+
# @param gear_uuid [String]
|
|
76
|
+
def gear_stats(gear_uuid)
|
|
77
|
+
connection.get("/gear-service/gear/stats/#{gear_uuid}")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Get default gear assignments per activity type.
|
|
81
|
+
def gear_defaults
|
|
82
|
+
connection.get("/gear-service/gear/user/#{user_profile_pk}/activityTypes")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Set or remove a gear item as default for an activity type.
|
|
86
|
+
# @param gear_uuid [String]
|
|
87
|
+
# @param activity_type [String]
|
|
88
|
+
# @param default [Boolean]
|
|
89
|
+
def set_gear_default(gear_uuid, activity_type, default: true)
|
|
90
|
+
if default
|
|
91
|
+
connection.put("/gear-service/gear/#{gear_uuid}/activityType/#{activity_type}/default/true")
|
|
92
|
+
else
|
|
93
|
+
connection.delete("/gear-service/gear/#{gear_uuid}/activityType/#{activity_type}")
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Link gear to an activity.
|
|
98
|
+
def link_gear(gear_uuid, activity_id)
|
|
99
|
+
connection.put("/gear-service/gear/link/#{gear_uuid}/activity/#{activity_id}")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Unlink gear from an activity.
|
|
103
|
+
def unlink_gear(gear_uuid, activity_id)
|
|
104
|
+
connection.put("/gear-service/gear/unlink/#{gear_uuid}/activity/#{activity_id}")
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Get activities associated with a gear item.
|
|
108
|
+
# @param gear_uuid [String]
|
|
109
|
+
# @param limit [Integer]
|
|
110
|
+
def gear_activities(gear_uuid, limit: 20)
|
|
111
|
+
connection.get(
|
|
112
|
+
"/activitylist-service/activities/#{gear_uuid}/gear",
|
|
113
|
+
params: { "start" => 0, "limit" => limit }
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GarminConnect
|
|
4
|
+
module API
|
|
5
|
+
# Daily health and wellness data endpoints.
|
|
6
|
+
module Health
|
|
7
|
+
# Get daily activity summary (steps, calories, distance, etc.).
|
|
8
|
+
# @param date [Date, String] the date (YYYY-MM-DD)
|
|
9
|
+
def daily_summary(date = today)
|
|
10
|
+
connection.get(
|
|
11
|
+
"/usersummary-service/usersummary/daily/#{display_name}",
|
|
12
|
+
params: { "calendarDate" => format_date(date) }
|
|
13
|
+
)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
alias_method :stats, :daily_summary
|
|
17
|
+
|
|
18
|
+
# Get combined daily stats and body composition.
|
|
19
|
+
# @param date [Date, String]
|
|
20
|
+
def stats_and_body(date = today)
|
|
21
|
+
{
|
|
22
|
+
"stats" => daily_summary(date),
|
|
23
|
+
"body_composition" => body_composition(date, date)
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Get heart rate data for a day.
|
|
28
|
+
# @param date [Date, String]
|
|
29
|
+
def heart_rates(date = today)
|
|
30
|
+
connection.get(
|
|
31
|
+
"/wellness-service/wellness/dailyHeartRate/#{display_name}",
|
|
32
|
+
params: { "date" => format_date(date) }
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Get resting heart rate for a date range.
|
|
37
|
+
# @param start_date [Date, String]
|
|
38
|
+
# @param end_date [Date, String]
|
|
39
|
+
def resting_heart_rate(start_date, end_date = start_date)
|
|
40
|
+
connection.get(
|
|
41
|
+
"/userstats-service/wellness/daily/#{display_name}",
|
|
42
|
+
params: {
|
|
43
|
+
"fromDate" => format_date(start_date),
|
|
44
|
+
"untilDate" => format_date(end_date),
|
|
45
|
+
"metricId" => 60
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Get Heart Rate Variability data.
|
|
51
|
+
# @param date [Date, String]
|
|
52
|
+
def hrv(date = today)
|
|
53
|
+
connection.get("/hrv-service/hrv/#{format_date(date)}")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Get sleep data for a day.
|
|
57
|
+
# @param date [Date, String]
|
|
58
|
+
def sleep_data(date = today)
|
|
59
|
+
connection.get(
|
|
60
|
+
"/wellness-service/wellness/dailySleepData/#{display_name}",
|
|
61
|
+
params: { "date" => format_date(date), "nonSleepBufferMinutes" => 60 }
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Get all-day stress data.
|
|
66
|
+
# @param date [Date, String]
|
|
67
|
+
def stress(date = today)
|
|
68
|
+
connection.get("/wellness-service/wellness/dailyStress/#{format_date(date)}")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Get body battery daily report.
|
|
72
|
+
# @param start_date [Date, String]
|
|
73
|
+
# @param end_date [Date, String]
|
|
74
|
+
def body_battery(start_date = today, end_date = start_date)
|
|
75
|
+
connection.get(
|
|
76
|
+
"/wellness-service/wellness/bodyBattery/reports/daily",
|
|
77
|
+
params: {
|
|
78
|
+
"startDate" => format_date(start_date),
|
|
79
|
+
"endDate" => format_date(end_date)
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Get body battery events (sleep, activities, naps).
|
|
85
|
+
# @param date [Date, String]
|
|
86
|
+
def body_battery_events(date = today)
|
|
87
|
+
connection.get("/wellness-service/wellness/bodyBattery/events/#{format_date(date)}")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Get steps chart data for a day.
|
|
91
|
+
# @param date [Date, String]
|
|
92
|
+
def steps_data(date = today)
|
|
93
|
+
connection.get(
|
|
94
|
+
"/wellness-service/wellness/dailySummaryChart/#{display_name}",
|
|
95
|
+
params: { "date" => format_date(date) }
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Get floors climbed data for a day.
|
|
100
|
+
# @param date [Date, String]
|
|
101
|
+
def floors(date = today)
|
|
102
|
+
connection.get("/wellness-service/wellness/floorsChartData/daily/#{format_date(date)}")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Get daily respiration data.
|
|
106
|
+
# @param date [Date, String]
|
|
107
|
+
def respiration(date = today)
|
|
108
|
+
connection.get("/wellness-service/wellness/daily/respiration/#{format_date(date)}")
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Get daily SpO2 (blood oxygen) data.
|
|
112
|
+
# @param date [Date, String]
|
|
113
|
+
def spo2(date = today)
|
|
114
|
+
connection.get("/wellness-service/wellness/daily/spo2/#{format_date(date)}")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Get daily intensity minutes.
|
|
118
|
+
# @param date [Date, String]
|
|
119
|
+
def intensity_minutes(date = today)
|
|
120
|
+
connection.get("/wellness-service/wellness/daily/im/#{format_date(date)}")
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Get all-day events (auto-detected activities, etc.).
|
|
124
|
+
# @param date [Date, String]
|
|
125
|
+
def daily_events(date = today)
|
|
126
|
+
connection.get(
|
|
127
|
+
"/wellness-service/wellness/dailyEvents",
|
|
128
|
+
params: { "calendarDate" => format_date(date) }
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Request reload of offloaded data for a date.
|
|
133
|
+
# @param date [Date, String]
|
|
134
|
+
def request_reload(date = today)
|
|
135
|
+
connection.post("/wellness-service/wellness/epoch/request/#{format_date(date)}")
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GarminConnect
|
|
4
|
+
module API
|
|
5
|
+
# Advanced health metrics, scores, and biometric endpoints.
|
|
6
|
+
module Metrics
|
|
7
|
+
# Get VO2 Max data.
|
|
8
|
+
# @param date [Date, String]
|
|
9
|
+
def max_metrics(date = today)
|
|
10
|
+
d = format_date(date)
|
|
11
|
+
connection.get("/metrics-service/metrics/maxmet/daily/#{d}/#{d}")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Get training readiness score.
|
|
15
|
+
# @param date [Date, String]
|
|
16
|
+
def training_readiness(date = today)
|
|
17
|
+
connection.get("/metrics-service/metrics/trainingreadiness/#{format_date(date)}")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Get morning-only training readiness (filters training_readiness result).
|
|
21
|
+
# @param date [Date, String]
|
|
22
|
+
def morning_training_readiness(date = today)
|
|
23
|
+
data = training_readiness(date)
|
|
24
|
+
return data unless data.is_a?(Array)
|
|
25
|
+
|
|
26
|
+
data.select { |entry| entry["calendarDate"] == format_date(date) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Get aggregated training status.
|
|
30
|
+
# @param date [Date, String]
|
|
31
|
+
def training_status(date = today)
|
|
32
|
+
connection.get("/metrics-service/metrics/trainingstatus/aggregated/#{format_date(date)}")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Get endurance score.
|
|
36
|
+
# @param date [Date, String] single day, OR
|
|
37
|
+
# @param start_date [Date, String, nil] range start
|
|
38
|
+
# @param end_date [Date, String, nil] range end
|
|
39
|
+
# @param aggregation [String] "weekly" or "daily"
|
|
40
|
+
def endurance_score(date = nil, start_date: nil, end_date: nil, aggregation: "weekly")
|
|
41
|
+
if start_date && end_date
|
|
42
|
+
connection.get(
|
|
43
|
+
"/metrics-service/metrics/endurancescore/stats",
|
|
44
|
+
params: {
|
|
45
|
+
"startDate" => format_date(start_date),
|
|
46
|
+
"endDate" => format_date(end_date),
|
|
47
|
+
"aggregation" => aggregation
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
else
|
|
51
|
+
connection.get(
|
|
52
|
+
"/metrics-service/metrics/endurancescore",
|
|
53
|
+
params: { "calendarDate" => format_date(date || today) }
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Get hill score.
|
|
59
|
+
# @param date [Date, String] single day, OR
|
|
60
|
+
# @param start_date [Date, String, nil] range start
|
|
61
|
+
# @param end_date [Date, String, nil] range end
|
|
62
|
+
# @param aggregation [String] "daily" or "weekly"
|
|
63
|
+
def hill_score(date = nil, start_date: nil, end_date: nil, aggregation: "daily")
|
|
64
|
+
if start_date && end_date
|
|
65
|
+
connection.get(
|
|
66
|
+
"/metrics-service/metrics/hillscore/stats",
|
|
67
|
+
params: {
|
|
68
|
+
"startDate" => format_date(start_date),
|
|
69
|
+
"endDate" => format_date(end_date),
|
|
70
|
+
"aggregation" => aggregation
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
else
|
|
74
|
+
connection.get(
|
|
75
|
+
"/metrics-service/metrics/hillscore",
|
|
76
|
+
params: { "calendarDate" => format_date(date || today) }
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Get race predictions (5k, 10k, half, full marathon).
|
|
82
|
+
# @param type [String, nil] nil for latest, or "daily"/"monthly" for range
|
|
83
|
+
# @param start_date [Date, String, nil]
|
|
84
|
+
# @param end_date [Date, String, nil]
|
|
85
|
+
def race_predictions(type: nil, start_date: nil, end_date: nil)
|
|
86
|
+
if type && start_date && end_date
|
|
87
|
+
connection.get(
|
|
88
|
+
"/metrics-service/metrics/racepredictions/#{type}/#{display_name}",
|
|
89
|
+
params: {
|
|
90
|
+
"fromCalendarDate" => format_date(start_date),
|
|
91
|
+
"toCalendarDate" => format_date(end_date)
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
else
|
|
95
|
+
connection.get("/metrics-service/metrics/racepredictions/latest/#{display_name}")
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Get fitness age.
|
|
100
|
+
# @param date [Date, String]
|
|
101
|
+
def fitness_age(date = today)
|
|
102
|
+
connection.get("/fitnessage-service/fitnessage/#{format_date(date)}")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# --- Biometrics ---
|
|
106
|
+
|
|
107
|
+
# Get latest lactate threshold data.
|
|
108
|
+
def lactate_threshold
|
|
109
|
+
connection.get("/biometric-service/biometric/latestLactateThreshold")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Get lactate threshold history over a date range.
|
|
113
|
+
# @param start_date [Date, String]
|
|
114
|
+
# @param end_date [Date, String]
|
|
115
|
+
# @param aggregation [String] "daily", "weekly", "monthly", or "yearly"
|
|
116
|
+
def lactate_threshold_history(start_date, end_date, aggregation: "daily")
|
|
117
|
+
params = {
|
|
118
|
+
"sport" => "RUNNING",
|
|
119
|
+
"aggregation" => aggregation,
|
|
120
|
+
"aggregationStrategy" => "LATEST"
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
{
|
|
124
|
+
"speed" => connection.get(
|
|
125
|
+
"/biometric-service/stats/lactateThresholdSpeed/range/#{format_date(start_date)}/#{format_date(end_date)}",
|
|
126
|
+
params: params
|
|
127
|
+
),
|
|
128
|
+
"heart_rate" => connection.get(
|
|
129
|
+
"/biometric-service/stats/lactateThresholdHeartRate/range/#{format_date(start_date)}/#{format_date(end_date)}",
|
|
130
|
+
params: params
|
|
131
|
+
),
|
|
132
|
+
"power" => connection.get(
|
|
133
|
+
"/biometric-service/stats/functionalThresholdPower/range/#{format_date(start_date)}/#{format_date(end_date)}",
|
|
134
|
+
params: params
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Get latest cycling FTP (Functional Threshold Power).
|
|
140
|
+
def cycling_ftp
|
|
141
|
+
connection.get("/biometric-service/biometric/latestFunctionalThresholdPower/CYCLING")
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# --- Historical / Trends ---
|
|
145
|
+
|
|
146
|
+
# Get daily steps over a date range (auto-chunks at 28 days).
|
|
147
|
+
# @param start_date [Date, String]
|
|
148
|
+
# @param end_date [Date, String]
|
|
149
|
+
def daily_steps(start_date, end_date)
|
|
150
|
+
chunked_request(start_date, end_date, 28) do |chunk_start, chunk_end|
|
|
151
|
+
connection.get("/usersummary-service/stats/steps/daily/#{format_date(chunk_start)}/#{format_date(chunk_end)}")
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Get weekly steps.
|
|
156
|
+
# @param end_date [Date, String]
|
|
157
|
+
# @param weeks [Integer]
|
|
158
|
+
def weekly_steps(end_date = today, weeks: 52)
|
|
159
|
+
connection.get("/usersummary-service/stats/steps/weekly/#{format_date(end_date)}/#{weeks}")
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Get weekly stress.
|
|
163
|
+
# @param end_date [Date, String]
|
|
164
|
+
# @param weeks [Integer]
|
|
165
|
+
def weekly_stress(end_date = today, weeks: 52)
|
|
166
|
+
connection.get("/usersummary-service/stats/stress/weekly/#{format_date(end_date)}/#{weeks}")
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Get weekly intensity minutes.
|
|
170
|
+
# @param start_date [Date, String]
|
|
171
|
+
# @param end_date [Date, String]
|
|
172
|
+
def weekly_intensity_minutes(start_date, end_date = today)
|
|
173
|
+
connection.get("/usersummary-service/stats/im/weekly/#{format_date(start_date)}/#{format_date(end_date)}")
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GarminConnect
|
|
4
|
+
module API
|
|
5
|
+
# User profile and settings endpoints.
|
|
6
|
+
module User
|
|
7
|
+
# Get the current user's profile settings (measurement system, sleep, etc.).
|
|
8
|
+
def user_settings
|
|
9
|
+
connection.get("/userprofile-service/userprofile/user-settings")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Get the current user's profile configuration.
|
|
13
|
+
def user_profile
|
|
14
|
+
connection.get("/userprofile-service/userprofile/settings")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Get personal information for a display name.
|
|
18
|
+
# @param display_name [String] defaults to the logged-in user
|
|
19
|
+
def personal_information(display_name = self.display_name)
|
|
20
|
+
connection.get("/userprofile-service/userprofile/personal-information/#{display_name}")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Get the full name of the logged-in user.
|
|
24
|
+
def full_name
|
|
25
|
+
@full_name
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Get the display name of the logged-in user.
|
|
29
|
+
def display_name
|
|
30
|
+
@display_name
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Get the measurement system (statute_us, metric, etc.).
|
|
34
|
+
def unit_system
|
|
35
|
+
@unit_system
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GarminConnect
|
|
4
|
+
module API
|
|
5
|
+
# Menstrual cycle, pregnancy, lifestyle logging, and GraphQL endpoints.
|
|
6
|
+
module Wellness
|
|
7
|
+
# Get menstrual cycle data for a specific date.
|
|
8
|
+
# @param date [Date, String]
|
|
9
|
+
def menstrual_data(date = today)
|
|
10
|
+
connection.get("/periodichealth-service/menstrualcycle/dayview/#{format_date(date)}")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Get menstrual cycle calendar for a date range.
|
|
14
|
+
# @param start_date [Date, String]
|
|
15
|
+
# @param end_date [Date, String]
|
|
16
|
+
def menstrual_calendar(start_date, end_date)
|
|
17
|
+
connection.get(
|
|
18
|
+
"/periodichealth-service/menstrualcycle/calendar/#{format_date(start_date)}/#{format_date(end_date)}"
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Get pregnancy summary snapshot.
|
|
23
|
+
def pregnancy_summary
|
|
24
|
+
connection.get("/periodichealth-service/menstrualcycle/pregnancysnapshot")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Get daily lifestyle logging data.
|
|
28
|
+
# @param date [Date, String]
|
|
29
|
+
def lifestyle_logging(date = today)
|
|
30
|
+
connection.get("/lifestylelogging-service/dailyLog/#{format_date(date)}")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Execute a GraphQL query against Garmin's gateway.
|
|
34
|
+
# @param query [String] the GraphQL query string
|
|
35
|
+
# @param variables [Hash] query variables
|
|
36
|
+
# @param operation_name [String, nil]
|
|
37
|
+
def graphql(query, variables: {}, operation_name: nil)
|
|
38
|
+
body = { "query" => query, "variables" => variables }
|
|
39
|
+
body["operationName"] = operation_name if operation_name
|
|
40
|
+
|
|
41
|
+
connection.post("/graphql-gateway/graphql", body: body)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|