mirror of
https://github.com/sstent/go-garth.git
synced 2026-01-26 09:03:00 +00:00
sync
This commit is contained in:
@@ -7,8 +7,6 @@ import (
|
||||
"time"
|
||||
|
||||
"garmin-connect/garth/client"
|
||||
"garmin-connect/garth/errors"
|
||||
"garmin-connect/garth/utils"
|
||||
)
|
||||
|
||||
// DailyBodyBatteryStress represents complete daily Body Battery and stress data
|
||||
@@ -116,30 +114,17 @@ func (d *DailyBodyBatteryStress) Get(day time.Time, client *client.Client) (any,
|
||||
dateStr := day.Format("2006-01-02")
|
||||
path := fmt.Sprintf("/wellness-service/wellness/dailyStress/%s", dateStr)
|
||||
|
||||
response, err := client.ConnectAPI(path, "GET", nil)
|
||||
data, err := client.ConnectAPI(path, "GET", nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if response == nil {
|
||||
if len(data) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
responseMap, ok := response.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, &errors.IOError{GarthError: errors.GarthError{
|
||||
Message: "Invalid response format"}}
|
||||
}
|
||||
|
||||
snakeResponse := utils.CamelToSnakeDict(responseMap)
|
||||
|
||||
jsonBytes, err := json.Marshal(snakeResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result DailyBodyBatteryStress
|
||||
if err := json.Unmarshal(jsonBytes, &result); err != nil {
|
||||
if err := json.Unmarshal(data, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"garmin-connect/garth/client"
|
||||
"garmin-connect/garth/utils"
|
||||
)
|
||||
|
||||
// HRVSummary represents Heart Rate Variability summary data
|
||||
@@ -13,7 +17,9 @@ type HRVSummary struct {
|
||||
CalendarDate time.Time `json:"calendarDate"`
|
||||
StartTimestampGMT time.Time `json:"startTimestampGmt"`
|
||||
EndTimestampGMT time.Time `json:"endTimestampGmt"`
|
||||
// Add other fields from Python implementation
|
||||
WeeklyAvg float64 `json:"weeklyAvg"`
|
||||
LastNightAvg float64 `json:"lastNightAvg"`
|
||||
Baseline float64 `json:"baseline"`
|
||||
}
|
||||
|
||||
// HRVReading represents an individual HRV reading
|
||||
@@ -26,6 +32,16 @@ type HRVReading struct {
|
||||
SignalQuality float64 `json:"signalQuality"`
|
||||
}
|
||||
|
||||
// TimestampAsTime converts the reading timestamp to time.Time using timeutils
|
||||
func (r *HRVReading) TimestampAsTime() time.Time {
|
||||
return utils.ParseTimestamp(r.Timestamp)
|
||||
}
|
||||
|
||||
// RRSeconds converts the RR interval to seconds
|
||||
func (r *HRVReading) RRSeconds() float64 {
|
||||
return float64(r.RRInterval) / 1000.0
|
||||
}
|
||||
|
||||
// HRVData represents complete HRV data
|
||||
type HRVData struct {
|
||||
UserProfilePK int `json:"userProfilePk"`
|
||||
@@ -34,19 +50,125 @@ type HRVData struct {
|
||||
BaseData
|
||||
}
|
||||
|
||||
// Validate ensures HRVSummary fields meet requirements
|
||||
func (h *HRVSummary) Validate() error {
|
||||
if h.WeeklyAvg < 0 {
|
||||
return errors.New("WeeklyAvg must be non-negative")
|
||||
}
|
||||
if h.LastNightAvg < 0 {
|
||||
return errors.New("LastNightAvg must be non-negative")
|
||||
}
|
||||
if h.Baseline < 0 {
|
||||
return errors.New("Baseline must be non-negative")
|
||||
}
|
||||
if h.CalendarDate.IsZero() {
|
||||
return errors.New("CalendarDate must be set")
|
||||
}
|
||||
if h.StartTimestampGMT.IsZero() || h.EndTimestampGMT.IsZero() {
|
||||
return errors.New("Timestamps must be set")
|
||||
}
|
||||
if h.EndTimestampGMT.Before(h.StartTimestampGMT) {
|
||||
return errors.New("EndTimestampGMT must be after StartTimestampGMT")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate ensures HRVReading fields meet requirements
|
||||
func (r *HRVReading) Validate() error {
|
||||
if r.StressLevel < 0 || r.StressLevel > 100 {
|
||||
return fmt.Errorf("StressLevel must be between 0-100, got %d", r.StressLevel)
|
||||
}
|
||||
if r.HeartRate <= 0 {
|
||||
return fmt.Errorf("HeartRate must be positive, got %d", r.HeartRate)
|
||||
}
|
||||
if r.RRInterval <= 0 {
|
||||
return fmt.Errorf("RRInterval must be positive, got %d", r.RRInterval)
|
||||
}
|
||||
if r.SignalQuality < 0 || r.SignalQuality > 1 {
|
||||
return fmt.Errorf("SignalQuality must be between 0-1, got %f", r.SignalQuality)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate ensures HRVData meets all requirements
|
||||
func (h *HRVData) Validate() error {
|
||||
if h.UserProfilePK <= 0 {
|
||||
return errors.New("UserProfilePK must be positive")
|
||||
}
|
||||
if err := h.HRVSummary.Validate(); err != nil {
|
||||
return fmt.Errorf("HRVSummary validation failed: %w", err)
|
||||
}
|
||||
for i, reading := range h.HRVReadings {
|
||||
if err := reading.Validate(); err != nil {
|
||||
return fmt.Errorf("HRVReading[%d] validation failed: %w", i, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DailyVariability calculates the average RR interval for the day
|
||||
func (h *HRVData) DailyVariability() float64 {
|
||||
if len(h.HRVReadings) == 0 {
|
||||
return 0
|
||||
}
|
||||
var total float64
|
||||
for _, r := range h.HRVReadings {
|
||||
total += r.RRSeconds()
|
||||
}
|
||||
return total / float64(len(h.HRVReadings))
|
||||
}
|
||||
|
||||
// MinHRVReading returns the reading with the lowest RR interval
|
||||
func (h *HRVData) MinHRVReading() HRVReading {
|
||||
if len(h.HRVReadings) == 0 {
|
||||
return HRVReading{}
|
||||
}
|
||||
min := h.HRVReadings[0]
|
||||
for _, r := range h.HRVReadings {
|
||||
if r.RRInterval < min.RRInterval {
|
||||
min = r
|
||||
}
|
||||
}
|
||||
return min
|
||||
}
|
||||
|
||||
// MaxHRVReading returns the reading with the highest RR interval
|
||||
func (h *HRVData) MaxHRVReading() HRVReading {
|
||||
if len(h.HRVReadings) == 0 {
|
||||
return HRVReading{}
|
||||
}
|
||||
max := h.HRVReadings[0]
|
||||
for _, r := range h.HRVReadings {
|
||||
if r.RRInterval > max.RRInterval {
|
||||
max = r
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
// ParseHRVReadings converts values array to structured readings
|
||||
func ParseHRVReadings(valuesArray [][]any) []HRVReading {
|
||||
readings := make([]HRVReading, 0)
|
||||
readings := make([]HRVReading, 0, len(valuesArray))
|
||||
for _, values := range valuesArray {
|
||||
if len(values) < 6 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract values with type assertions
|
||||
// Add parsing logic based on Python implementation
|
||||
timestamp, _ := values[0].(int)
|
||||
stressLevel, _ := values[1].(int)
|
||||
heartRate, _ := values[2].(int)
|
||||
rrInterval, _ := values[3].(int)
|
||||
status, _ := values[4].(string)
|
||||
signalQuality, _ := values[5].(float64)
|
||||
|
||||
readings = append(readings, HRVReading{
|
||||
// Initialize fields
|
||||
Timestamp: timestamp,
|
||||
StressLevel: stressLevel,
|
||||
HeartRate: heartRate,
|
||||
RRInterval: rrInterval,
|
||||
Status: status,
|
||||
SignalQuality: signalQuality,
|
||||
})
|
||||
}
|
||||
sort.Slice(readings, func(i, j int) bool {
|
||||
@@ -57,8 +179,29 @@ func ParseHRVReadings(valuesArray [][]any) []HRVReading {
|
||||
|
||||
// Get implements the Data interface for HRVData
|
||||
func (h *HRVData) Get(day time.Time, client *client.Client) (any, error) {
|
||||
// Implementation to be added
|
||||
return nil, nil
|
||||
dateStr := day.Format("2006-01-02")
|
||||
path := fmt.Sprintf("/wellness-service/wellness/dailyHrvData/%s?date=%s",
|
||||
client.Username, dateStr)
|
||||
|
||||
data, err := client.ConnectAPI(path, "GET", nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var hrvData HRVData
|
||||
if err := json.Unmarshal(data, &hrvData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := hrvData.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("HRV data validation failed: %w", err)
|
||||
}
|
||||
|
||||
return hrvData, nil
|
||||
}
|
||||
|
||||
// List implements the Data interface for concurrent fetching
|
||||
|
||||
@@ -6,8 +6,6 @@ import (
|
||||
"time"
|
||||
|
||||
"garmin-connect/garth/client"
|
||||
"garmin-connect/garth/errors"
|
||||
"garmin-connect/garth/utils"
|
||||
)
|
||||
|
||||
// SleepScores represents sleep scoring data
|
||||
@@ -45,43 +43,28 @@ func (d *DailySleepDTO) Get(day time.Time, client *client.Client) (any, error) {
|
||||
path := fmt.Sprintf("/wellness-service/wellness/dailySleepData/%s?nonSleepBufferMinutes=60&date=%s",
|
||||
client.Username, dateStr)
|
||||
|
||||
response, err := client.ConnectAPI(path, "GET", nil)
|
||||
data, err := client.ConnectAPI(path, "GET", nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if response == nil {
|
||||
if len(data) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
responseMap, ok := response.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, &errors.IOError{GarthError: errors.GarthError{
|
||||
Message: "Invalid response format"}}
|
||||
var response struct {
|
||||
DailySleepDTO *DailySleepDTO `json:"dailySleepDto"`
|
||||
SleepMovement []SleepMovement `json:"sleepMovement"`
|
||||
}
|
||||
|
||||
snakeResponse := utils.CamelToSnakeDict(responseMap)
|
||||
|
||||
dailySleepDto, exists := snakeResponse["daily_sleep_dto"].(map[string]interface{})
|
||||
if !exists || dailySleepDto["id"] == nil {
|
||||
return nil, nil // No sleep data
|
||||
}
|
||||
|
||||
jsonBytes, err := json.Marshal(snakeResponse)
|
||||
if err != nil {
|
||||
if err := json.Unmarshal(data, &response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result struct {
|
||||
DailySleepDTO *DailySleepDTO `json:"daily_sleep_dto"`
|
||||
SleepMovement []SleepMovement `json:"sleep_movement"`
|
||||
if response.DailySleepDTO == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(jsonBytes, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// List implements the Data interface for concurrent fetching
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"garmin-connect/garth/client"
|
||||
@@ -15,25 +16,66 @@ type WeightData struct {
|
||||
Weight float64 `json:"weight"` // in kilograms
|
||||
BMI float64 `json:"bmi"`
|
||||
BodyFatPercentage float64 `json:"bodyFatPercentage"`
|
||||
BoneMass float64 `json:"boneMass"` // in kg
|
||||
MuscleMass float64 `json:"muscleMass"` // in kg
|
||||
Hydration float64 `json:"hydration"` // in kg
|
||||
BaseData
|
||||
}
|
||||
|
||||
// Validate checks if weight data contains valid values
|
||||
func (w *WeightData) Validate() error {
|
||||
if w.Weight <= 0 {
|
||||
return errors.New("invalid weight value")
|
||||
return fmt.Errorf("invalid weight value")
|
||||
}
|
||||
if w.BMI < 10 || w.BMI > 50 {
|
||||
return fmt.Errorf("BMI out of valid range")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get implements the Data interface for WeightData
|
||||
func (w *WeightData) Get(day time.Time, client *client.Client) (any, error) {
|
||||
// Implementation to be added
|
||||
return nil, nil
|
||||
startDate := day.Format("2006-01-02")
|
||||
endDate := day.Format("2006-01-02")
|
||||
path := fmt.Sprintf("/weight-service/weight/dateRange?startDate=%s&endDate=%s",
|
||||
startDate, endDate)
|
||||
|
||||
data, err := client.ConnectAPI(path, "GET", nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var response struct {
|
||||
WeightList []WeightData `json:"weightList"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(response.WeightList) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
weightData := response.WeightList[0]
|
||||
// Convert grams to kilograms
|
||||
weightData.Weight = weightData.Weight / 1000
|
||||
weightData.BoneMass = weightData.BoneMass / 1000
|
||||
weightData.MuscleMass = weightData.MuscleMass / 1000
|
||||
weightData.Hydration = weightData.Hydration / 1000
|
||||
|
||||
return weightData, nil
|
||||
}
|
||||
|
||||
// List implements the Data interface for concurrent fetching
|
||||
func (w *WeightData) List(end time.Time, days int, client *client.Client, maxWorkers int) ([]any, error) {
|
||||
// Implementation to be added
|
||||
return []any{}, nil
|
||||
results, errs := w.BaseData.List(end, days, client, maxWorkers)
|
||||
if len(errs) > 0 {
|
||||
// Return first error for now
|
||||
return results, errs[0]
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user