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.
@@ -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