This is a description of my setup for logging pomodoros in Trello and then submitting them to Beeminder.
The basic idea is the following. I use the pomodroido pomodoro timer for android. Finished pomodoros are submitted from my android phone (using an ssh plugin for tasker) to Trello via a personal server where I have some appropriate scripts. My scripts log the pomodoros in Trello as checked items added to a checklist named “Pomodoros” of whatever Trello card is in the “Doing” Trello list of my board. So my Trello board contains cards which represent tasks and each such card has a checklist named “Pomodoros”. The number of checked items in such a checklist represents the number of pomodoros I have already finished for the task represented by the card. My server then periodically scans my Trello board, submitting any new checked items to my totalpomodoros Beeminder goal. The submitted data points have as a comment the title of the corresponding Trello card.
If anyone wants to try this, a more detailed description follows.
I use Trello as my to-do list app. Each Trello card represents a task I need to work on. When I start working on a given task, I move the corresponding card to a list called Doing
. For example, this is how my Doing
list looks right now:
Once I have moved the card there, I start a pomodoro on my android phone. I use the pomodroido pro pomodoro timer for this:
The nice thing about the pro version of this app is that it allows me to invoke tasker when a pomodoro starts or when a pomodoro stops. Specifically, I use tasker as follows.
I have two tasker profiles. Both are of type event -> 3rd party -> Pomodroido
. One of them is for when I start a pomodoro on pomodroido:
Figure 1:
The other profile is for when a pomodoro stops:
Figure 2:
The first profile invokes a task that I call Start Pomodroido
(Figure 3). The second invokes a task that I call Stop Pomodroido
(Figure 5)
Figure 3:
The Start Pomodroido
task (Figure 3) starts by invoking another helper task that saves the time when the pomodoro started (Figure 4):
Figure 4:
I save this time so that when a pomodoro stops I can distinguish between the following two cases:
- The pomodoro stopped because it was completed.
- The pomodoro stopped because I clicked the stop button on Pomodroido.
Once this task has been invoked, the Start Pomodroido
task (Figure 3) resumes showing a pop-up to remind me to be effective, i.e., to work on what matters and not waste pomodoros doing unimportant stuff. The task then finishes with some encouraging words uttered by the android text-to-speech engine.
Now to what happens when a pomodoro stops. The task that gets executed in this case is called Stop Pomodroido
and looks like this:
Figure 5:
This task begins by invoking another helper tasker:
Figure 6:
What the helper task does is store in a variable called Secondsworked
the amount of time that elapsed since the last pomodoro started. That is, it assigns to Secondsworked
the value %TIMES - %Pomodorostart
.
After this helper task, Stop Pomodroido
(Figure 5) invokes a second helper task:
Figure 7:
This helper task uses an SSH plugin for tasker to connect to my personal Linux server if %Secondsworked > 1499
, i.e., if 25 minutes of pomodoro time have been logged. On that server it invokes a python script I call add-pomodoro-to-trello.py
. The script makes use of a Python wrapper for the Trello API and looks like this:
#!/usr/bin/python3
# author: David Gessner
"""
Simple script that adds pomodoros as checked items to the first card in the
'Doing' list of a trello board.
"""
from trello import TrelloClient
client = TrelloClient(
api_key='XXXXXXXXXXXXXXXXXXXXXXXX',
api_secret='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
token='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
token_secret='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
)
todo_list_board = client.get_board("XXXXXXXXX")
doing_list = todo_list_board.get_list("XXXXXXXXXXXXXXXXX")
doing_cards = doing_list.list_cards()
if doing_cards == []:
doing_list.add_card("Unknown task")
first_card = doing_list.list_cards()[0]
first_card.fetch()
for cl in first_card.checklists:
if cl.name == "Pomodoros":
pomodoro_cl = cl
break
else:
first_card.add_checklist("Pomodoros", ["1"])
pomodoro_cl = first_card.checklists[0]
pomodoro_items = pomodoro_cl.items
for item in pomodoro_items:
# check the first unchecked pomodoro
if not item['checked']:
pomodoro_cl.set_checklist_item(item['name'], True)
break
else:
last_pomodoro_item = pomodoro_items[-1]
last_pomodoro_number = int(last_pomodoro_item['name'].split(" ")[0])
pomodoro_cl.add_checklist_item(str(last_pomodoro_number + 1), True)
print("Pomodoro submitted to '{}'".format(first_card.name.decode()))
The above code basically logs a completed pomodoro in Trello as a new checked item that is added to a checklist named Pomodoros
of whatever Trello card is in the Doing
Trello list of my board. Right now it has already logged two pomodoros for the writing of this post:
Figure 8:
After the helper task from Figure 7 finishes, my Stop Pomodroido
task (Figure 5) continues by doing some other stuff, but which is irrelevant for the tracking of Pomodoros. (It invokes another script on my server that I use to update a percentile feedback graph, increases another tasker variable that tracks completed pomodoros and that I use in other tasker tasks, and utters some congratulations using text-to-speech).
With what I have described so far I can log pomodoros in Trello cards as checked check list items. Now, to beemind this, what I have is another python script that is periodically executed by a cron job on my server. This script uses the same Trello API python wrapper as the previous script to look for completed Pomodoros in my Trello cards. Then it submits these Pomodoros to Beeminder using a python wrapper to the Beeminder API. This second script is called beemind-trello-pomodoros.py
and the code is the following:
#!/usr/bin/python3
# author: David Gessner
"""
Script that submits checked pomodoros in trello lists to beeminder.
"""
import datetime
from collections import defaultdict
from time import time
import os
from trello import TrelloClient
from beeminder.beeminder import Beeminder
client = TrelloClient(
api_key='XXXXXXXXXXXXXXXXXXXXXXXXXXX',
api_secret='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
token='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
token_secret='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
)
todo_list_board = client.get_board("XXXXXXXXXX")
new_pomodoros = defaultdict(int)
timestamp_now = round(time())
timestr = datetime.datetime.fromtimestamp(
timestamp_now
).strftime('%Y-%m-%d %H:%M:%S')
for L in todo_list_board.all_lists():
if L.name == b'Someday/maybe':
# Don't look for pomodoros in the Someday/maybe list
continue
# cards to check for unsubmitted Pomodoros
cards_to_check = L.list_cards()
if cards_to_check == []:
continue
for card in cards_to_check:
card.fetch()
for cl in card.checklists:
if cl.name == "Pomodoros":
for item in cl.items:
if item['checked'] and not "beeminded" in item['name']:
cl.rename_checklist_item(
item['name'], item['name'] + " (beeminded " +
str(timestr) + ")")
new_pomodoros[card] += 1
def log_beeminder_data(path, datapoints):
with open(path, "a") as log:
for datapoint in datapoints:
timestr = datetime.datetime.fromtimestamp(
datapoint['timestamp']).strftime('%Y %m %d')
valuestr = str(datapoint['value'])
comment = datapoint['comment']
log.write('{} {} "{}"\n'.format(timestr, valuestr, comment))
bee = Beeminder("XXXXXXXXXXXXXXXXXXX")
totalpomodoros_goal = bee.goal("totalpomodoros")
totalpomodoro_datapoints = [
{
'timestamp': timestamp_now,
'value': pomodoro_count,
'comment': card.name.decode()
} for card, pomodoro_count in new_pomodoros.items()
]
if totalpomodoro_datapoints:
print(
"Submitting the following data to {}: {}".format(
"totalpomodoros", totalpomodoro_datapoints))
bee.create_all("totalpomodoros", totalpomodoro_datapoints)
script_dir = os.path.dirname(os.path.realpath(__file__))
log_beeminder_data(
script_dir + "/totalpomodoros.log",
totalpomodoro_datapoints)
# Change this if you change the beeminder goal with highest priority
prioritygoal_slug = "fttrs-journal-paper"
if prioritygoal_slug:
prioritygoal_datapoints = [
{
'timestamp': timestamp_now,
'value': pomodoro_count,
'comment': card.name.decode()
} for card, pomodoro_count in new_pomodoros.items()
if "prioritygoal" in [
label_dict['name'] for label_dict in card.labels]
]
if prioritygoal_datapoints:
print(
"Submitting the following data to {}: {}".format(
prioritygoal_slug, prioritygoal_datapoints))
bee.create_all(prioritygoal_slug, prioritygoal_datapoints)
log_beeminder_data(
script_dir + "/{}.log".format(prioritygoal_slug),
prioritygoal_datapoints)
Note that the script actually submits the pomodoros to two goals of mine: I have a totalpomodoros goal where all my pomodoros are logged, and then I have a priority pomodoro goal where only pomodoros are logged that correspond to the project in my work-life that is currently of highest priority. To decide whether to submit to the second I use Trello labels.
I just executed the script, and my Trello card about writing this post now looks like this:
Figure 9:
Note in Figure 9 that comments are used to timetag Beeminded pomodoros. This is to keep track of which pomodoro check list items have already been submitted to Beeminder.
My totalpomodoros beeminder goal now looks like this:
Figure 10:
Notice at the very bottom the datapoint 26 3 "Write about how I use pomodoros with Beeminder"
. It was just submitted by my script by scanning my Trello board. The comment of the datapoint comes from the title of the Trello card from which the 3 submitted pomodoros were retrieved.
Finally, now that I have written this post, I simply move my card from the Doing
list to the Done
list: