In my daily work I use a CLI tool called todo.txt coupled with an add-on called punch to track the time spent on chunks of work. Tasks are added and organized by todo.txt, and punch is used to punch in and out of those tasks. Additionally, punch has a few useful reporting and archiving functions. In combination, todo.txt and punch make for a simple and useful tool that has served me well for several years.
Useful, but not perfect. A big drawback of punch is that there is no obvious indicator of the timer’s state. To see if the timer is active and the elapsed time, you must issue a cli command. Because of this, I often find myself forgetting to punch in to a new task or out at the end of the day. This obviously leads to some inaccuracies and frustrations in my time keeping. I’ve seen a few solutions to this. The simplest solution would likely be some sort of widget to display the running timer somewhere on your windows manager. However, while messing around with some old panaplex displays, I began thinking of using them in a small desktop clock build. This clock could potentially have a serial input which would allow it to display information from the punch timer. So I’ve found myself thinking about what would it take to implement something like this.
Last year I was interested in finding a solution for simple USB to serial communications. Through that research I stumbled upon the CP21x devices from Silicon Labs. Eventually I settled on the CP2104 USB to UART bridge and ordered a development board. These chips save a lot of work establishing onboard USB. They appear to an OS as a typical USB device, auto negotiate baud, and simply pass along serial communications to and from the USB port. There are some advanced configurations available to you if you wish, but by in large the CP2104 is a plug and play chip.
At the time I wrote a test in Python to echo keypresses from my computer to the device. It turns out the chip is as easy to use as promised, and is a perfect solution to interface my computer with a microcontroller in a potential desktop clock.
I thought it would be worthwhile to write a short proof of concept to output data from punch to serial USB. To display this data I used a SparkFun serLCD I had laying around. This wound up being a relatively simple task. I don’t think its useful walking through all of the code, but for the sake of my memory I want to point out a couple things that I learned along the way. If you’re curious you can check out the complete script on my github. Please forgive my clumbsy Python. The code can be cleaned up and there is certainly a more elegant solution.
Punch writes tasks and timestamps to a file, called
punch.dat to catalogue time spent on tasks. In order for my program to be aware of active tasks and elapsed time, it needs to watch this file for changes. The Watchdog library handled this task nicely.
Watchdog establishes an API to monitor file system CRUD events. After digging in a bit, I found it to be friendly and effective, and can be summarized in a few lines of code:
eventHandler = PunchDatEvent()
observer = Observer()
# When you're done watching.
Observer is a thread class to which you can schedule monitoring of a given path, and assign it an event handler to act on filesystem CRUD events observed in the given directory. Starting and stopping this thread is as easy as firing the respective methods. Most of the heavy lifting is handled by the event handler you extend from the included
FileSystemEventHandler class (
PunchDatEvent in the above snippet). Watchdog handles everything else. Quite nice.
A thread has been established for the Watchdog observer to handle filesystem monitoring, but what about the serial communications? While I could likely handle this in the main program loop, I thought it could be a good opportunity to spin off another thread just for this purpose. Painful memories linger of C/C++ multithreading from days gone by, but I have no experience with it in Python. The task here seemed simple enough to dip a toe in and give it a try.
After spending some time with the core threading library documentation I was surprised to see just how simple this could be (granted this is a very basic application). To sum up in a couple of lines:
tickTockThread = Thread(target=eventHandler.tickTock)
Instantiate a thread object and give it a target object to run. In this case the target is a method I’ve defined within the same event handler used with the observer thread. Then simply start it. It will run until the target method terminates, at which time the thread is destroyed.
Reading through the docs, I can see how this can get more complex in a hurry. However, I was pleasantly surprised with how simple this was for my small task.
I now have a decent mechanism for sending my time tracker data via serial over USB to an eventual desk clock. There are a few critical things to add to the code as I move forward with the project, such as:
- Daemonize the script for Systemd.
- Establish some methods of detecting the presence of the USB device.
- Rework the serial packets sent to fit whatever protocol is eventually used for the clock, rather than the one built into serLCD.
However, for now, this is fine.