Tracking pomodoros in Trello and beeminding them


#1

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:

  1. The pomodoro stopped because it was completed.
  2. 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:


Beemind pomodoros automatically with Complice
Beeminding pomodoros with clockwork tomato and tasker
#2

This is awesome! I like how interactive and fun this feels, with very little fiddling once it’s all set up.

What’s the rest of your effectiveness reminder? And the TTS encouragement?


#3

If you like this you’ll probably also like the latest post on the RescueTime blog:


#4

The full effectiveness reminder is “Do what’s important. Remember: Being busy is not the same as being productive.”

The encouragement is “Come on! This ain’t nothing but a peanut. Let’s rock this pomodoro!”


#5

I like that this winds up keeping a tally of how many pomodoros it took to complete the task! (if I’m understanding it correctly). I’ve been kind of wanting to get back to using Trello for task list management because it’s a really nice interface. In particular I like the that you can use a short handle for the card’s title to keep your full-view task list neat, but then you can use the description etc on the back of the card to get (almost) arbitrarily detailed about the task.

(And also of course I adore that you did pomodoros on writing up this post about doing pomodoros… :smile:)

B


#6

You understood correctly: I know for each of my tasks the number of pomodoros I dedicated to it. Moreover, I can see the pomodoro count on the front of each Trello card. So when I glance at my Trello board, I can see for each task how many pomodoros it has consumed.

And yes: Trello is great! But much more so with pomodoros and beeminder! This system just works extremely well for me. And I love that I can do everything from my android phone (for some pomodoros I’m not in front of the computer): I just use the Trello android app to move a card to the doing list and then start the timer in pomodroido. From there the pomodoros are automatically tallied in the corresponding Trello card and submitted to Beeminder. By using Trello labels it’s also easy to control to which Beeminder goal a pomodoro should be submitted.


#7

@davitenio How did this work for you so far?

I intend to implement your python scripts. It is super helpful and it is almost exactly what I want. Thanks!

The only thing that is out of of my comfort zone, is working with a Linux server.

What server did you choose? If I understood it well, you mean a cloud server like the Amazon cloud servers, right?

Any suggestion for a cheap Linux server to run such python scripts? Especially that I will only need them for running these scripts which will not take too much resources nor bandwidth.

Last question, why you didn’t try to run the python code on the Android instead to run the code on the server?

Tasker can execute the python script when you start/end a pomodoro with an app like this

and recently it even has a tasker plugin.


#8

It’s working great! I’m still using it.

I’m using a ramnode server, which costs $15/year: https://www.ramnode.com/vps.php

I did not know that tasker could run Python scripts.


#9

@davitenio Reporting back. It worked. I used your 1st script without the beeminder api.

The python script worked fine on Android using the apps in my previous post. So I did not need a personal server. The ability to run python scripts with or without tasker in Android will open the door for a lot of interesting stuff.

Thanks!