Package gfit: Steps: Calculate diff to previously stored data point.
authorFlorian Forster <ff@octo.it>
Sat, 13 Jan 2018 21:35:12 +0000 (22:35 +0100)
committerFlorian Forster <ff@octo.it>
Sat, 13 Jan 2018 21:35:12 +0000 (22:35 +0100)
Google Fit will happily store multiple data points with the exact same
dataSourceID and start and end times. The web frontend then seems to
discard duplicate entries.

This commit reads existing data points and then calculates the
difference to the existing entries.

gfit/gfit.go

index ff4c26d..8fe0d54 100644 (file)
@@ -19,6 +19,8 @@ import (
 const (
        csrfToken = "@CSRFTOKEN@"
        userID    = "me"
+
+       dataTypeNameSteps = "com.google.step_count.delta"
 )
 
 var oauthConfig = &oauth2.Config{
@@ -141,12 +143,49 @@ func (c *Client) DataSetPatch(ctx context.Context, dataSourceID string, points [
        return nil
 }
 
-func (c *Client) SetSteps(ctx context.Context, steps int, date time.Time) error {
-       const dataTypeName = "com.google.step_count.delta"
+func (c *Client) Steps(ctx context.Context, startTime, endTime time.Time) (int, time.Time, error) {
+       dataSourceID := DataStreamID(&fitness.DataSource{
+               Type: "raw",
+               DataType: &fitness.DataType{
+                       Name: dataTypeNameSteps,
+               },
+       })
+       datasetID := fmt.Sprintf("%d-%d", startTime.UnixNano(), endTime.UnixNano())
+
+       res, err := c.Service.Users.DataSources.Datasets.Get(userID, dataSourceID, datasetID).Context(ctx).Do()
+       if err != nil {
+               log.Errorf(ctx, "c.Service.Users.DataSources.Datasets.Get(%q, %q) = %v",
+                       dataSourceID, datasetID, err)
+               return 0, time.Time{}, err
+       }
+
+       if len(res.Point) == 0 {
+               return 0, startTime, nil
+       }
+
+       steps := 0
+       maxEndTime := startTime
+       for _, p := range res.Point {
+               pointEndTime := time.Unix(0, p.EndTimeNanos).In(startTime.Location())
+               value := p.Value[0].IntVal
+
+               steps += int(value)
+               if maxEndTime.Before(pointEndTime) {
+                       maxEndTime = pointEndTime
+               }
+       }
+
+       log.Debugf(ctx, "Google Fit has data points until %v: %d steps", maxEndTime, steps)
+       return steps, maxEndTime, nil
+}
+
+func (c *Client) SetSteps(ctx context.Context, totalSteps int, startOfDay time.Time) error {
+       if totalSteps == 0 {
+               return nil
+       }
 
        dataSourceID, err := c.DataSourceCreate(ctx, &fitness.DataSource{
                Application:    Application(ctx),
-               DataStreamId:   "", // COMPUTED
                DataStreamName: "", // "daily summary"?
                DataType: &fitness.DataType{
                        Field: []*fitness.DataTypeField{
@@ -155,24 +194,41 @@ func (c *Client) SetSteps(ctx context.Context, steps int, date time.Time) error
                                        Name:   "steps",
                                },
                        },
-                       Name: dataTypeName,
+                       Name: dataTypeNameSteps,
                },
                Name: "Step Count",
                Type: "raw",
+               // Type: "derived",
        })
        if err != nil {
                return err
        }
 
+       endOfDay := startOfDay.Add(24 * time.Hour).Add(-1 * time.Nanosecond)
+       prevSteps, startTime, err := c.Steps(ctx, startOfDay, endOfDay)
+       if totalSteps == prevSteps {
+               return nil
+       }
+       diffSteps := totalSteps - prevSteps
+       if diffSteps < 0 {
+               log.Warningf(ctx, "c.Steps returned %d steps, but current count is %d", prevSteps, totalSteps)
+               diffSteps = totalSteps
+       }
+       endTime := endOfDay
+       if now := time.Now().In(startOfDay.Location()); now.Before(endOfDay) {
+               endTime = now
+       }
+       log.Debugf(ctx, "new data point: %v-%v %d steps", startTime, endTime, diffSteps)
+
        return c.DataSetPatch(ctx, dataSourceID, []*fitness.DataPoint{
                &fitness.DataPoint{
                        ComputationTimeMillis: time.Now().UnixNano() / 1000000,
-                       DataTypeName:          dataTypeName,
-                       StartTimeNanos:        date.UnixNano(),
-                       EndTimeNanos:          date.Add(24 * time.Hour).Add(-1 * time.Nanosecond).UnixNano(),
+                       DataTypeName:          dataTypeNameSteps,
+                       StartTimeNanos:        startTime.UnixNano(),
+                       EndTimeNanos:          endTime.UnixNano(),
                        Value: []*fitness.Value{
                                &fitness.Value{
-                                       IntVal: int64(steps),
+                                       IntVal: int64(diffSteps),
                                },
                        },
                },