11 "google.golang.org/appengine/log"
14 // SleepLevel is the depth of sleep.
16 // The Fitbit API provides these in one of two ways. The newer representation
17 // is ("deep", "light", "rem" and "wake"), the previous (older) representation
18 // is ("asleep", "restless" and "awake"). We map the old representation to the
19 // newer one, because we'd need to do that for Google Fit anyway. This is done
28 SleepLevelUnknown SleepLevel = iota
35 // SleepStage is one stage during a user's sleep.
36 type SleepStage struct {
42 // Sleep is one period of sleep that is broken down into multiple stages.
47 type byTime []SleepStage
49 func (t byTime) Len() int { return len(t) }
50 func (t byTime) Less(i, j int) bool { return t[i].StartTime.Before(t[j].StartTime) }
51 func (t byTime) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
53 func parseSleep(ctx context.Context, data []byte, loc *time.Location) (*Sleep, error) {
67 if err := json.Unmarshal(data, &parsed); err != nil {
71 var allStages []rawData
72 for _, s := range parsed.Sleep {
73 if len(s.Levels.Data) != 0 {
74 allStages = append(allStages, s.Levels.Data...)
76 if len(s.Levels.ShortData) != 0 {
77 allStages = append(allStages, s.Levels.ShortData...)
82 for _, stg := range allStages {
83 tm, err := time.ParseInLocation("2006-01-02T15:04:05.999", stg.DateTime, loc)
85 log.Warningf(ctx, "unable to parse time: %q", stg.DateTime)
91 case "deep", "asleep":
92 level = SleepLevelDeep
93 case "light", "restless":
94 level = SleepLevelLight
98 level = SleepLevelWake
100 log.Warningf(ctx, "unknown sleep level: %q", stg.Level)
104 ret.Stages = append(ret.Stages, SleepStage{
106 EndTime: tm.Add(time.Duration(stg.Seconds) * time.Second),
111 if len(ret.Stages) == 0 && len(allStages) != 0 {
112 return nil, fmt.Errorf("parsing sleep stages failed")
115 sort.Sort(byTime(ret.Stages))
120 // Sleep returns the sleep log for date t. Times are parsed in the same timezone as t.
121 func (c *Client) Sleep(ctx context.Context, t time.Time) (*Sleep, error) {
122 url := fmt.Sprintf("https://api.fitbit.com/1.2/user/%s/sleep/date/%s.json",
123 c.fitbitUserID, t.Format("2006-01-02"))
125 res, err := c.client.Get(url)
129 defer res.Body.Close()
131 data, err := ioutil.ReadAll(res.Body)
135 log.Debugf(ctx, "GET %s -> %s", url, data)
137 return parseSleep(ctx, data, t.Location())