From 43223a02eecd68642404b09c3085f435722cb144 Mon Sep 17 00:00:00 2001 From: Florian Forster Date: Thu, 11 Jan 2018 21:45:43 +0100 Subject: [PATCH] Write step count summary to Google Fit. --- gfit/gfit.go | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ gfitsync.go | 18 +++++++++++++---- 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/gfit/gfit.go b/gfit/gfit.go index 028eee0..ed8f7cb 100644 --- a/gfit/gfit.go +++ b/gfit/gfit.go @@ -4,11 +4,14 @@ import ( "context" "fmt" "net/http" + "time" "github.com/octo/gfitsync/app" "golang.org/x/oauth2" oauth2google "golang.org/x/oauth2/google" fitness "google.golang.org/api/fitness/v1" + "google.golang.org/appengine" + "google.golang.org/appengine/log" ) var oauthConfig = &oauth2.Config{ @@ -24,6 +27,14 @@ var oauthConfig = &oauth2.Config{ const csrfToken = "@CSRFTOKEN@" +func Application(ctx context.Context) *fitness.Application { + return &fitness.Application{ + Name: "Fitbit to Google Fit sync", + Version: appengine.VersionID(ctx), + DetailsUrl: "", // optional + } +} + func AuthURL() string { return oauthConfig.AuthCodeURL(csrfToken, oauth2.AccessTypeOffline) } @@ -60,3 +71,58 @@ func NewClient(ctx context.Context, u *app.User) (*Client, error) { Service: service, }, nil } + +func (c *Client) SetSteps(ctx context.Context, steps int, date time.Time) error { + const userID = "me" + const dataTypeName = "com.google.step_count.delta" + dataSource := &fitness.DataSource{ + Application: Application(ctx), + DataStreamName: "", // "daily summary"? + DataType: &fitness.DataType{ + Field: []*fitness.DataTypeField{ + &fitness.DataTypeField{ + Format: "integer", + Name: "steps", + }, + }, + Name: dataTypeName, + }, + Name: "Step Count", + Type: "raw", + } + + dataSource, err := c.Service.Users.DataSources.Create(userID, dataSource).Context(ctx).Do() + if err != nil { + log.Errorf(ctx, "c.Service.Users.DataSources.Create() = (%+v, %v)", dataSource, err) + return err + } + dataSourceID := dataSource.DataStreamId + + startTimeNanos := date.UnixNano() + endTimeNanos := date.Add(86399999999999 * time.Nanosecond).UnixNano() + datasetID := fmt.Sprintf("%d-%d", startTimeNanos, endTimeNanos) + dataset := &fitness.Dataset{ + MinStartTimeNs: startTimeNanos, + MaxEndTimeNs: endTimeNanos, + Point: []*fitness.DataPoint{ + &fitness.DataPoint{ + ComputationTimeMillis: time.Now().UnixNano() / 1000000, + DataTypeName: dataTypeName, + StartTimeNanos: startTimeNanos, + EndTimeNanos: endTimeNanos, + Value: []*fitness.Value{ + &fitness.Value{ + IntVal: int64(steps), + }, + }, + }, + }, + } + + dataset, err = c.Service.Users.DataSources.Datasets.Patch(userID, dataSourceID, datasetID, dataset).Context(ctx).Do() + if err != nil { + log.Errorf(ctx, "c.Service.Users.DataSources.Datasets.Patch() = (%+v, %v)", dataset, err) + return err + } + return nil +} diff --git a/gfitsync.go b/gfitsync.go index 2bc3b73..22f4c56 100644 --- a/gfitsync.go +++ b/gfitsync.go @@ -218,21 +218,31 @@ func handleNotification(ctx context.Context, s *fitbit.Subscription) error { if err != nil { return err } - c, err := fitbit.NewClient(ctx, s.OwnerID, u) + + tm, err := time.Parse("2006-01-02", s.Date) if err != nil { return err } - tm, err := time.Parse("2006-01-02", s.Date) + fitbitClient, err := fitbit.NewClient(ctx, s.OwnerID, u) if err != nil { return err } - summary, err := c.ActivitySummary(tm) + summary, err := fitbitClient.ActivitySummary(tm) if err != nil { return err } - log.Debugf(ctx, "ActivitySummary for %s = %+v", u.Email, summary) + + gfitClient, err := gfit.NewClient(ctx, u) + if err != nil { + return err + } + + if err := gfitClient.SetSteps(ctx, summary.Summary.Steps, tm); err != nil { + return fmt.Errorf("gfitClient.SetSteps(%d) = %v", summary.Summary.Steps, err) + } + return nil } -- 2.11.0