10 "google.golang.org/appengine/log"
13 // SleepLevel is the depth of sleep.
15 // The Fitbit API provides these in one of two ways. The newer representation
16 // is ("deep", "light", "rem" and "wake"), the previous (older) representation
17 // is ("asleep", "restless" and "awake"). We map the old representation to the
18 // newer one, because we'd need to do that for Google Fit anyway. This is done
27 SleepLevelUnknown SleepLevel = iota
34 // SleepStage is one stage during a user's sleep.
35 type SleepStage struct {
41 // Sleep is one period of sleep that is broken down into multiple stages.
46 func parseSleep(ctx context.Context, data []byte, loc *time.Location) (*Sleep, error) {
52 var jsonSleep struct {
58 if err := json.Unmarshal(data, &jsonSleep); err != nil {
62 rawStages := jsonSleep.Levels.Data
63 if len(jsonSleep.Levels.ShortData) != 0 {
64 rawStages = jsonSleep.Levels.ShortData
68 for _, stg := range rawStages {
69 tm, err := time.ParseInLocation("2006-01-02T15:04:05.999", stg.DateTime, loc)
71 log.Warningf(ctx, "unable to parse time: %q", stg.DateTime)
77 case "deep", "asleep":
78 level = SleepLevelDeep
79 case "light", "restless":
80 level = SleepLevelLight
84 level = SleepLevelWake
86 log.Warningf(ctx, "unknown sleep level: %q", stg.Level)
90 ret.Stages = append(ret.Stages, SleepStage{
92 EndTime: tm.Add(time.Duration(stg.Seconds) * time.Second),
97 if len(ret.Stages) == 0 && len(jsonSleep.Levels.Data) != 0 {
98 return nil, fmt.Errorf("parsing sleep stages failed")
104 // Sleep returns the sleep log for date t. Times are parsed in the same timezone as t.
105 func (c *Client) Sleep(ctx context.Context, t time.Time) (*Sleep, error) {
106 url := fmt.Sprintf("https://api.fitbit.com/1.2/user/%s/sleep/date/%s.json",
107 c.fitbitUserID, t.Format("2006-01-02"))
109 res, err := c.client.Get(url)
113 defer res.Body.Close()
115 data, err := ioutil.ReadAll(res.Body)
119 log.Debugf(ctx, "GET %s -> %s", url, data)
121 return parseSleep(ctx, data, t.Location())