Scriptone

[Python] Automatically Record Sleep Time to Toggl

Table of Contents

Create a program using Python to automatically write sleep time to Toggl.

Overview

To squeeze out free time or relaxation, I use several task management tools. As my main tool, I use TickTick to track completed tasks. However, I noticed it's hard to see how much time I spent on each one.
In terms of input visibility, Toggl allows me to easily measure time like a stopwatch and quickly record the work done. It also has an API for automatically accumulating data. Additionally, with its CSV export function, I can easily store and analyze data.
As a starting point, I'll write a process to automatically register sleep time from my smartwatch to Toggl and gradually get familiar with the API.

Input Data

I use Garmin's Venu 2 as my smartwatch.

Garmin does not have an official API, but there is a library that can access the API. I'll use this to obtain sleep logs and convert the data for the Toggl API. Although I'm using a Garmin watch this time, Fitbit has an official API as well.

Since it's a third-party product, the returns from the API will differ, but I can still obtain sleep records. As long as I can get the data, other watches should work fine for recording sleep time.

Additionally, by modifying the data, I can input various things beyond sleep, such as exercise time from other APIs, or record human activities, or even use it for non-human time tracking purposes, which could be interesting.

Coding

The repository for the code I wrote is at the following URL:
https://github.com/rmc8/Tool-for-transcribing-sleep-records-to-toggl

Main

The main processing is written in main.py. It pulls settings from a YAML file or command line and executes the program.

The command line is structured as follows.

python main.py {number of days to go back}

If you input 1 for the number of days to go back via the command line, it will try to retrieve sleep records from the previous day up to the day of program execution. If you input 30, it will retrieve records from 30 days prior. If you omit the argument, it will only try to get data for the current day. It's a bit inconvenient not being able to specify dates, but I've simplified it since I want to set it up for automatic registration with a task scheduler.

The YAML file is as follows.

time_zone: 9  
toggl:  
  token: # Required  
  time_entry:  
  workspace_id:  
  project_id:  
  task_id:  
  billable: False # Not required  
  tags:  
  - 睡眠  
garmin:  
  email: # Required  
  password: # Required

YAML loading is implemented in util.py using PyYAML, a convenient library for reading YAML files into dictionary format. All contents of this configuration file are values to be passed to the API. The time_zone is the offset from UTC. The rest will be explained later.

After that, it retrieves sleep time from the Garmin API, converts the data for Toggl, and registers the sleep time to Toggl via the API. Finally, it outputs the response from the Toggl API as a CSV file in the ./output directory.

Garmin API

https://github.com/rmc8/Tool-for-transcribing-sleep-records-to-toggl/blob/main/pkg/garmin.py

Data acquisition from Garmin uses the unofficial library as mentioned earlier. Therefore, there are no clues for data acquisition other than the repository's README, source code, and developer tools analysis. While reading the source code reveals endpoints and POST contents, I'll omit that here.

API Connection

class GarminAPI:  
 def __init__(self, email: str, password: str, time_deff: int = 9):  
  self.client = Garmin(email, password)  
  self.client.login()  
  self.time_deff: int = time_deff

The GarminAPI class connects to the API upon initialization. Authentication uses the same user information as Garmin Connect. Enter the email address and password in config.yml. Since returns from the Garmin API are in UTC (GMT), I added time_deff for time difference adjustment.

Sleep Log Acquisition

Use the get_sleep_logs method to retrieve sleep information in bulk. Although encapsulated, the get_sleep_data method from the garminconnect library quickly obtains a JSON including sleep time.
The dailySleepDTO value in that JSON contains bedtime and wake-up time in epoch seconds (GMT). The following processes convert these to Python's datetime format and adjust for the time difference.

@staticmethod
def epoc2date(epoc: int, time_deff: int):
 dt = datetime.fromtimestamp(epoc / 1000)
 return dt + timedelta(hours=time_deff)

start = self.epoc2date(display[“sleepStartTimestampGMT”], self.time_deff) end = self.epoc2date(display[“sleepEndTimestampGMT”], self.time_deff)

After that, the data is converted for Toggl. The dictionary contents are as follows.

Key

Value

Description

date

Date slept

Not used, but for reference

description

Description

Here, it describes when you went to sleep.
This can be considered as the title for now.

start

Start time

This corresponds to bedtime.

stop

End time

This corresponds to wake-up time.

duration

Duration

Time elapsed in seconds.

duration is explained in a complicated way in Toggl's official documentation, but it's used for the display in the yellow-marked section in the figure below.


It mentions that negatives are possible, which is unclear, but using it effectively could allow subtracting time spent awake during sleep. The others aren't particularly tricky, so I'll omit them for now.

Once the data for all dates is compiled with sleep times, it's converted to a Pandas DataFrame and returned to main.py.

Toggl API

https://github.com/rmc8/Tool-for-transcribing-sleep-records-to-toggl/blob/main/pkg/toggl.py

The Toggl API is summarized on GitHub. There's a Python client, but since I couldn't get it from PyPI and the usage wasn't clear without reading the source code, I wrote only the necessary processes based on the documentation.

Authentication for the API uses a token. It's located at the bottom of the Profile page. Enter this in config.yml under toggl > token. The other three IDs in toggl > time_entry are for recording in specific locations. billable is basically False since sleep doesn't directly generate profit. Additionally, tags can be set as a list for multiple tags. For now, I've set just one: - 睡眠.

Recording to Toggl

Use the create_time_entry method to record sleep time to Toggl. It takes the required values as arguments and uses keyword arguments and unpacking for others as needed.
stop isn't mandatory, but since duration can be tricky, including it ensures the end time is accurate.

Then, it generates a JSON payload based on the documentation and sends it via the _request method to register the sleep times in bulk through the API.

Log Output

Output the API results as a CSV file in the ./output directory. A sample is as follows.

id,wid,billable,start,stop,duration,description,tags,duronly,at,uid
2xxxxxxxxx,5xxxxxxxx,False,2021-09-25T23:46:00.000+09:00,2021-09-26T06:56:00.000+09:00,25800,Sleep time on 2021-09-25,['睡眠'],False,2021-09-26T08:59:03+00:00,7xxxxxxxx

Future Applications

I was able to automatically register sleep time. Similarly, for anything that can be automatically registered using smartwatches or IoT, I want to proceed with automatic registration first.

For TickTick, I'm using GAS or IFTTT for integration, and manually registering important tasks to organize what to do. If I can get this via API and select the tasks being done to control the stopwatch (start/stop) and (complete/incomplete), managing input and output would become easier.

Additionally, for parts without API, the app's functions are very simple, so using them as-is would make task management and analysis much easier.

Besides just using the Toggl app normally, there's still a lot of coding needed, but by reusing what's available and integrating via API, and since it's cloud-based for information sharing across all devices, it's very convenient. For personal use, it's free and has more than enough features, so I'm grateful.

Summary

Toggl is a great platform for managing and analyzing time. Although Japanese support is still limited, if you're not allergic to English, it's a highly useful and extensible tool. Please try combining it with various IoT devices or APIs for fun.