Brent Walther

Projects

Even though software engineering is my profession, I don't only code in the office. I'll occassionally dabble in software side-projects at home, too. This page is dedicated to hobby/side projects I've worked on outside of my job. It includes some academic projects too.

Budgeting with ledger-cli

[Permalink]

In my Personal Finance Philosophy post, I describe the importance of budgeting and acknowledge that it can be time consuming to do properly. Since I’m a software engineer by trade, I looked for software to help me with the job. I initially began doing double-entry accounting for all my personal finances I used GnuCash, an open-source accounting tool. It allowed me to understand the workflows, but the integrated expense matcher is terrible and I wanted something better.

I found the ledger-cli tool which is double-entry accounting software that uses a simple text file as your database. This allows you to use version control (git) to maintain/version it and write simple scripts to update and modify it (in addition to the already powerful CLI).

My new workflow for managing my budget looks like this:

  1. Every month, download CSV exports of transactions from my credit card and bank accounts.
  2. Use my JCF tool to automatically match the transactions and export them to a ledger-cli compatible file.
  3. Merge each ledger file into the main ledger.
  4. Use ledger-cli to export an updated master CSV and mapping file of all expenses.
  5. Check diffs using Git and commit the results if they look correct.
  6. Import the master expenses CSV into a spreadsheet and use a pivot table to break down the expenses in to budget categories.

If you want to skip the details, there are scripts and example files at the bottom.

Setup

To try and follow follow this workflow, you’ll need to get some of the software set up:

  1. To run JCF, you need to install the build system bazel.
  2. Use git to clone JCF.
  3. Either download a precompiled binary or build ledger-cli from source: https://www.ledger-cli.org/download.html
  4. Download the scripts the edit the variables.sh file with paths to your ledger files/executable.
  5. Create an initial master.accounts file. For an example list of accounts, see here.

Download transaction CSVs

Each month, I log in to my bank and credit card accounts and download all the transactions that occurred in the last month. I know that I could automate this even more using something like Plaid, but I’m a bit security paranoid so I still do it myself. If I can’t remember where I left off in any given account, I can invoke ledger to find out:

~/Development/ledger/ledger -f /path/to/master.ledger register "Account:Name" --tail 10 --sort date

After downloading each one, I use the linux scp program to copy them from my laptop to my server.

Match transactions and convert to ledger format

Once I have all my transaction CSV files, I transform each one to a matched ledger-compatible file using my JCF tool. The tool handles determining CSV column ordering, date format, and does account guessing or tab-completed manual matching. Here’s an example invocation of the tool:

cd ~/Development/jcf

bash match.sh /home/brentwalther/Downloads/1234_jul "Assets:Account 1234" /path/to/payee-account-mappings.tsv /path/to/master.accounts

You’ll need to have the master accounts file already set up (it powers the tab-completion in the matcher). If it’s your first time matching transactions, you won’t have any mappings yet (which requires you to use the tab-completed matching to start), but you should create an empty file near your master ledger. You’ll update the mappings file after every single CSV import.

Merge the new ledgers

After converting all the CSV files, I’ll have a collection of individual ledger files to merge in to the master ledger. I merge them one at a time using my merge.sh script, specifying the name of the account when I invoke it. Here’s an example:

bash merge.sh ~/Downloads/1234_jul.ledger

Export the new master CSV and mapping files

After merging an account, I use my export-expense-csv.sh and export-expense-mappings.sh scripts to update the master expense CSV and expense mapping files that live right beside my master ledger. I use git to maintain all these files.

Use git to maintain my databases

Next, I can use git diff to compare the new master ledger, CSV, and mapping files to the new ones (with newly merged transactions). If everything checks out, I git add them and git commit the updates. I do this after every single merge which ensures I never waste any work if I need to revert back to a previous state.

Use a spreadsheet with pivot table

Finally, I use a pivot table in Google Sheets to create a budget table that breaks down all the expense categories by month. The master expense CSV file can be imported to its own special sheet in one go (and replaced atomically) and the pivot table lives in a separate sheet.

Scripts

Variables

I keep paths to all the ‘global’ variables in a vars.sh file to make other scripts easier to produce. I build ledger from source but you can probably download a prebuilt binary too.

Check Balances

I use this primarily to quickly see what the last recorded transaction is for each of my credit card and bank accounts to know the beginning date for CSV exports on their websites.

Merge a converted ledger file

The JCF tool is designed to match all transactions from a CSV and exports a ledger compatible file, but you may need to make sure you aren’t imbalanced. When using the merge.sh, it’ll use the master.accounts file and --pedantic CLI option to make sure there are no rogue accounts and then it sorts and formats the whole master ledger. Afterwards, I can use any diff-tool to compare the updated file.

Export expense CSV

This is used to update the master CSV file I use in my budget spreadsheet. This creates a massive file that can be easily sorted/grouped by a pivot table.

Export expense mappings

This exports a large tab-separated-value (TSV) file as an input for the matcher built in to my JCF tool. It’s just a giant list descriptions and respective expense account separated by a tab.

Reformat

Sometimes I want to make some local edits in the ledger. In order to not worry about formatting, I just make the edits then run this over the ledger to reformat and sort it.

Example files

master.ledger

Here’s a small excerpt of my actual master ledger:

2020/05/27 * HEB FOOD STORES
    Liabilities:Credit Card:American Express Blue Cash Preferred                $-6.11
    Expenses:Groceries

2020/05/28 * TST* LAZARUS BREWING CO.
    Expenses:Food/Drink:Alcohol & Bars                                          $44.31
    Liabilities:Credit Card:Chase

2020/05/28 * AMZN Mktp US*M70CA3W41
    Expenses:Shopping:Amazon/Online                                              $6.48
    Liabilities:Credit Card:Capital One Venture

2020/05/28 * TARGET.COM  *
    Expenses:Shopping:Clothing                                                  $97.43
    Liabilities:Credit Card:Capital One Venture

2020/05/28 * SPOTIFY MUSIC SUBS USD RECURRING
    Liabilities:Credit Card:American Express Blue Cash Preferred               $-16.23
    Expenses:Bills:Streaming

2020/05/29 * Dividends Reinvested
    Assets:Investments:Personal:VWSTX                                           $13.48
    Income:Unrealized Capital Gains:Dividends (Tax Free)

2020/05/29 * Dividends Reinvested
    Assets:Investments:Personal:VWITX                                            $8.13
    Income:Unrealized Capital Gains:Dividends (Tax Free)

2020/05/29 * Dividends Reinvested
    Assets:Investments:Personal:VMMXX                                            $1.05
    Income:Unrealized Capital Gains:Dividends

2020/05/29 * MATTS EL R* MATTS EL R
    Expenses:Food/Drink:Restaurants                                             $53.92
    Liabilities:Credit Card:Chase

2020/05/29 * Kindle Svcs*M78CA4DA2
    Expenses:Shopping:Books                                                     $12.98
    Liabilities:Credit Card:Capital One Venture

master.accounts

My master accounts file is just a list of all accounts in the ledger preceeded with account (e.g. account Assets:Bank:Ally Checking). ledger-cli supports some fancy virtual account and commodity stuff but I don’t use any of it at the moment.

I’ve got an example list of accounts here.

expenses.csv

Here’s an example of the format of my master CSV file that’s generated using the export-expense-csv.sh script above:

"2020/04/03","Expenses:Groceries","78.65"
"2020/04/02","Expenses:Shopping:Amazon/Online","10.81"
"2020/04/01","Expenses:Bills:RentalInsurance","14.07"
"2020/04/01","Expenses:Food/Drink:Coffee Shops","27"
"2020/04/01","Expenses:Shopping:Amazon/Online","14.06"
"2020/04/01","Expenses:Food/Drink:Convenient Store","7.99"
"2020/03/31","Expenses:Food/Drink:Alcohol & Bars","40.57"
"2020/03/30","Expenses:Shopping:Amazon/Online","17.17"
"2020/03/30","Expenses:Food/Drink:Convenient Store","6.47"
"2020/03/30","Expenses:Food/Drink:Restaurants","43.96"
"2020/03/30","Expenses:Food/Drink:Restaurants","8"
"2020/03/29","Expenses:Food/Drink:Fast Food","23.14"
"2020/03/29","Expenses:Groceries","157.24"
"2020/03/28","Expenses:Auto:Gas","50.08"

payee-account-mappings.tsv

This is the input to my JCF tool and generated by the export-expense-mappings.sh script above. It’s simply the transaction description followed by a tab then the expense account it went to:

HOMEGOODS #503  Expenses:Shopping:Home
City of Austin   Expenses:Bills:Electric
Amazon.com    Expenses:Shopping:Amazon/Online
GOOGLE*GOOGLE FI        Expenses:Bills:Mobile Phone
WALGREENS Expenses:Food/Drink:Convenient Store
TORCHYS TACOS NORTHSHORE        Expenses:Food/Drink:Restaurants
CORNER STORE              TX      Expenses:Food/Drink:Convenient Store
CORNER STORE              TX      Expenses:Auto:Gas
LEIF JOHNSON BEN WHITE  Expenses:Auto:Repair and Maintenance
AMZN Mktp  Expenses:Shopping:Amazon/Online
TST* PINTHOUSE PIZZA - SO       Expenses:Food/Drink:Restaurants
MARKET@WORK  Expenses:Food/Drink:Fast Food
CHUY'S  Expenses:Food/Drink:Restaurants

Analyzing my 6.0L Powerstroke ODBII data

[Permalink]

I recently took my 2003 F-250 on a trip pulling a single-axle camper trailer behind it. I’ve had the truck now for a few months and am well aware of the shortcomings of the 6.0L Powerstroke engine but wanted to collect some data for my engine to use as justification and prioritization for upgrades. I’ve been looking at the parts offered by BulletProofDiesel for quite some time but didn’t want to unnecessarily upgrade parts without first establishing a baseline for engine health. Using a bluetooth ODBII adapter and the Torque Android app, I collected that data on this recent trip. I estimate the total combine weight of the trailer, cargo, and passengers to be ~3000 pounds (on top of the truck curb weight).

My 2003 F-250.

The engine oil cooler

As described in this BulletProofDiesel article [2], the engine oil cooler is one of the main things on a 6.0L Powerstroke that is prone to failures (especially on higher mileage vehicles). If it fails, the failure can quickly cascade to other parts (e.g. the EGR cooler) too. To know whether or not the oil cooler is beginning to cause problems for you, you need to know the temperatures of your engine oil and engine cooleant. It’s recommended that the temperature of the two differ no more than about 10-15 degrees Farhenheit [1].

I used the Torque Android app and a bluetooth ODBII adapter to monitor the two temperatures using the truck’s own sensors. The app polls the ECM for sensor values on a regular basis and can be configured to log specific values in a CSV file for export later. I monitored the values for an 8 hour trip between Palo Duro Canyon State Park (outside of Amarillo, TX) back to our home in Austin, TX.

After collecting the data it’s clear that replacing my oil cooler, potentially with an upgrade, is a priority repair for this truck. While we didn’t hit any snags along the trip, the engine oil ran quite hot. It ran 224°F on average and was as hot as 236°F during the trip. The coolant averaged 208°F. The difference between the two was 15.6°F on average but was as great as 25.5°F later on in the trip (after pulling for several hours).

I also measured the temperatures of the engine coolant and oil separately throughout the trip. The big dips you see in the graph are the truck cooling off while we stopped for a running refuel. If you squint, you can actually see average coolant temperature decreasing more quickly than the oil. The ambient outdoor temperature was well over 100°F in the panhandle but cooled down to around 95°F by the time we got home. I think the fact that the two trend lines don’t track perfectly is also a symptom of inefficient cooling since the OEM oil cooler cools the oil with the coolant itself [2].

Footnotes

Home Brewing

[Permalink]

A page dedicated to my love of beer.

Tree Service Brewing

I’ve been interested in trying homebrewing for a few years but it wasn’t until I had a second interested party that I finally decided to give it a try. My friend Trent told me he was interested in trying it out, that he has done it once before (solo), that he had some of the equipment already, and that he knew where the brew supply shop was. I was sold. There’s an Austin company called Brent’s Tree Service and we had an inside joke that led us to eventually settle on labeling (figuratively) our creations as “Tree Service” brewing.

Our setup is pretty simple. We brew in a giant aluminum pot over a propane burner. It isn’t perfect since the heat is concentrated in the middle but frequent stirring mitigates that issue. We’ve got a copper wort chiller coil that we submerge in ice when we need to transfer the boiled wort to the carboy for fermenting, and then we have a cheap 6.5 gallon plastic carboy to ferment in. We have only bottled out beer thus far (as opposed to kegging). Though I’ve had some folks suggest that kegging is more convenient, I prefer bottling so far since it’s easier to keep and share.

Hop Froth - Imperial Pale Ale

Our first beer was a standard IPA brewed with Cascade and Columbus hops. We bought an easy boxed ingredient kit that has everything you need besides the equipment and water.

We didn’t measure the original or specific gravity of this beer so weren’t sure how alcoholic it turned out but I can say it was pretty boozy. It was much sweeter than I expected but the hops still shined through very well. The head was very thick and aromatic.

Our grains steeping in the pot.
Our grains steeping in the pot.
The beer right after the boil, sealed in the carboy and ready to begin fermenting. The cap has an airlock to allow gas to escape during fermentation.
The beer right after the boil, sealed in the carboy and ready to begin fermenting. The cap has an airlock to allow gas to escape during fermentation.
The finished product. We had about 5 gallons of beer which fills many bottles.
The finished product. We had about 5 gallons of beer which fills many bottles.

Toasty Roasty - Imperial Stout w/ Coffee

After doing something really hoppy for our first brew, we wanted to switch and do something dark. We went with the ingredient kit again - this time an imperial stout caught our eye. I was interested in trying something with fewer hops and a stronger grain flavor.

This stout used Cara Munich Dark, Black Barley, and Chocolate Wheat grains and was finished off with some mild Columbus hops. Trent also does his own coffee roasting so he roasted some fresh coffee (Yemen Mokha Harasi), coarsely ground the beans, and put it in the carboy while fermenting. It turned out really incredible.

The taste profile for this beer was even better than I expected. It was not very sweet which allowed the grain flavor to come through really well, and the coffee was a very welcome addition. I think it did end up caffeinating the beer quite a bit. Again, we didn’t measure the gravity of the beer but it was probably even more boozy than our last. I would definitely make one of these again.

Steeping our grains. Look how dark it is already!
Steeping our grains. Look how dark it is already!
Trent roasted the beans in this coffee roaster he owns.
Trent roasted the beans in this coffee roaster he owns.
The finished beer. The head came out just right in my opinion and it poured very nicely. I should have rinsed my glassware out first though. Rookie mistake!
The finished beer. The head came out just right in my opinion and it poured very nicely. I should have rinsed my glassware out first though. Rookie mistake!

Sugar Daddy - Belgian Tripel

Trent’s first solo homebrew (before we started brewing together) was a belgian tripel and he really liked the way it turned out so we decided to brew one together for our third beer. I was surprised by the amount of sugar you need to put in a belgian tripel but it makes sense given how sweet and boozy they are. We had a mishap while brewing toasty roasty (our carboy got overfilled a little bit and blew the stopper off!) so we knew we needed to be more careful with the fermentation setup, especially in a beer that had plenty of food for the yeast.

Of our first three beers, this one actually turned out to be my least favorite but was still really good. We used a TON of sugar and it has a bit of a strange syrupy after taste. Shooting in the dark, I think more even heat distribution during out boiling process could have improved it some. Regardless, I still liked it.

The tripel during the boil. Very foamy!
The tripel during the boil. Very foamy!
Wort chilling using our fancy copper chiller coil. In my opinion, this is our best equipment purchase so far.
Wort chilling using our fancy copper chiller coil. In my opinion, this is our best equipment purchase so far.
To make sure we didn't have another ferment run over, this one was done in the boil pot with a towel covering it.
To make sure we didn't have another ferment run over, this one was done in the boil pot with a towel covering it.

Portable Custom Radio

[Permalink]

(a.k.a. radio cooler v2)

The custom radio cooler I built in college (and other prior art) has seen a lot of fun times between tailgates, pool days, tailgates, and hunting trips. However, it was built optimizing for battery life at the cost of portability (the 100 Ah battery weighs a ton!). I decided I wanted to build something sleeker and more portable, so I did.

The first step was choosing some type of case. Coolers are pretty convenient because they’re easy to cut through and they can be fairly water resistant but they are also difficult to seal well. I began looking at electrical junction boxes (and I still think one could work quite well) but many had awkward dimensions and I wasn’t sure if drilling or cutting through them would work well. I finally settled on a latching plastic case meant for electrical components or firearms. It seals well and has a convenient handle already installed. It also was long enough to fit three speakers.

Empty radio case.
Empty case.
Case with amplifier inside.
Amplifier sizing.

The case is quite spacious inside. I placed the amplifier in first to size it up but I had compared dimensions online before purchasing.

Inside of case with speakers, amplifier, and aluminum rails installed.
Installed speakers and some components.
Outside of case after installing speakers.
Outside of case with speakers installed.

I carefully measured and cut out holes for the speakers using a dremel tool. I used a tiny cutting wheel to get a rough shape first and then used one of the sanding heads to round them out. If I were to do it a second time, I would tape templates down from the start to get perfectly round holes. After cutting the holes, I installed the speakers using bolts and wing nuts as well as a bit of silicon sealant around the speaker beds.

The components on the inside are mounted via aluminum C-channel I found at Home Depot. It was inexpensive, reasonably strong, light weight, and shouldn’t rust if any moisture gets inside. The two T brackets ended up getting mounted behind the battery to C-channel two cross members. The battery is 12V/20Ah sealed unit I picked up on Amazon. The entire assembly is mounted to the case at 4 anchor points: 2 to the bottom of the battery and two at the place where the lower amplifier holes are.

Finished portable custom radio front.
Finished radio front w/ trickle charger.

The finished radio looks quite nice from the front and uses a small trickle charger to charge up. It sits easily upright though I would love to have a way to also sit it on its side so the three speakers were stacked. The sound is very directional (somehow to my surprise) so it’s best placed facing the direction you want the sound to go. The volume of the case with all components installed seems close to the right size to get good speaker suspension – bass is loud and even shakes the lid some.

The inside of the cooler is slightly messier than I’d like but it’s still a work in progress. The battery is held in place via paracord because I haven’t thought of a better way that isn’t too rigid (I don’t want to jar the battery too much). The switch is placed alongside a 2-port USB power hub and the bluetooth module. The hub shows the battery voltage to give an approximate idea of charge level. The bluetooth module only draws a few mA so I keep it permanently powered (switch controls the amplifier)..

Completed inside of portable custom radio.
Finished and wired inside.
Finished portable custom radio rear.
Finished radio rear w/ trickle charger.

The rear only contains 4 mounting bolts which can be easily sealed with silicon caulking. The radio is significantly more portable than the previous iteration and can be carried with one hand like a suit case.

Me holding the custom radio at my side.
Finished radio is very portable.

Parts List

Here’s a non-exhaustive list of what I used for this build. Including hardware one of these can be built for around $350. It’s way louder than the all-in-one stereo units you can find at Costco or Sams Club.

Radio Cooler

[Permalink]

A front 45 degree view of the radio cooler.
It cleans up pretty nice.
A rear 45 degree view of the radio cooler.
Bottle opener is used frequently.

After having seen a few in the wild and having one crummy proof-of-concept build, I ordered parts for and built a brand new radio cooler in the fall of 2013. It started with two speakers and grew to four and has since been used hundreds of times between tailgates, beaches, and poolsides. It lasts longer, is way louder, and is much more hardy than a lot of store-bought speaker systems. I built mine to be as water resistant as possible; I used all marine audio equipment and tried to make decent seals around the speakers and lid. It can get rained on or be thrown into a river float and still provide powerful loud sound for tens of hours.

Components

My radio cooler is built from mostly inexpensive components and was constructed such that any component can be swapped out quite easily. For this build, I used:</p>

So all together it costs around $275 to build one of these give or take some depending on what kind of battery you use. You can also use any type of cooler that you feel comfortable mounting the amplifier and speakers in.

Construction

A complete picture of the inside of the cooler.
A complete picture of the inside of the cooler.
An image of the tricle charger wired to the battery.
Trickle charger rests in the bottom and is hard wired to the battery with quick disconnect.

Putting one of these together is much easier than it looks. The battery essentially hooks straight to the amplifier and the amplifier straight to the speakers. After that, you just need to wire in a toggle switch from amplifier (+) to the amplifier toggle post. Plug in the RCA adapters in connecting the amp to your phone (or other aux/RCA output). Turn it on and start jamming!

A common question I get about this cooler is whether or not it can actually hold drinks. The answer for my build is no. In order to maintain low moisture inside the cooler, I try not to invite any liquids (except perhaps sunscreen) inside. It can become an issue if your cooler has a drain plug. Mine eventually failed and could no longer float in a river tube without being filled with water. It is worth mentioning however that my amplifier continued to work even with fully submerged power and speaker terminals.

An image of the wired and mounted speaker.
Speakers are mounted with bolts and have wires soldered (not required).
An image of the amplifier, toggle switch, and battery.
Toggle switch hangs loose but could be mounted. Battery connects to amplifier. RCA on top.

I measured and cut the holes in the cooler using a reciprocating saw. Speaker and amplifier components were mounted using bolts with wingnuts through pre-drilled holes. I soldered the wires to the speakers but this is not required. All amplifier connections are using terminal connectors and the battery rests on the bottom with small brackets preventing it from sliding. There is a fair bit of storage space inside.

Wiring

Below is a wiring diagram showing how where all the wires connect up with one another.

A wiring diagram demonstrating how to wire up a speaker cooler.
Click the diagram to download a large version.

Minecraft Controller Mod

[Permalink]

Source Code

Minecraft Controller Mod Settings Page Screenshot.
A screenshot of the mod's settings page.

Developed in early 2018, this is a mod that uses MinecraftForge to enable controller use for the PC version of Minecraft. Instructions for setting up and using are in the repository readme. Once setup, it’s as simple as turning on your controller and pressing “run” in the IDE. The Mod uses XInput and should work anywhere XInput is supported.

Spotify Playlist Tools

[Permalink]

Source Code

A playlist diff tool and a playlist copier for Spotify. Built with Angular during Summer 2015. I has grandiose dreams of building something with the pitch “version control for your playlist”. The idea was that you could ‘fork’ a playlist which would copy all the songs and you could add any of your own that you want but you could also merge in updates from the original playlist too. The use case would be to fork one of the spotify-updated playlists and make it your own.

Tearable Cloth Simulation

[Permalink]

A physics simulation of a tearable (and gravity affected) cloth. The cloth is represented as a large spring mesh. I built this in Spring 2015 for Dr. Keyser’s Physically Based Modeling (CSCE-649) class at Texas A&M using three.js and vanilla Javascript. It uses Runge-Kutta fourth-order method to integrate movement over time.

It’s not optimized and evaluating as many nodes as the simulation has in a browser using Javascript is difficult. Thus you’ll notice it is a bit buggy, especially on slower computers or mobile browsers. You can use A and D keys to rotate the view and the T key to “tear” the cloth.

Demo

Rigid Body Demo

[Permalink]

A simulation of a rigid-body cube that I built Spring 2015 for Dr. Keyser’s Physically Based Modeling (CSCE-649) class at Texas A&M using three.js and vanilla Javascript. It uses basic euler integration by default which can be unstable but has an option to use Runge-Kutta second-order method for greater stability.

You can use A and D keys to rotate the view. A rigid body (in physical simulation) is most basically described as a dice or other hard object which simply transfers energy within itself rather than absorbing it via a spring force.

Demo

Springy Mesh Cube

[Permalink]

A simulation of a cube represented as a springy mesh (connected corners) that I built Spring 2015 for Dr. Keyser’s Physically Based Modeling (CSCE-649) class at Texas A&M using three.js and vanilla Javascript. It uses Runge-Kutta Euler method to integrate movement over time. It uses Runge Kutta second-order by default but can optionally use fourth-order for greater stability.

You can use A and D keys to rotate the view.

Demo

Boid Flocking Demo

[Permalink]

A simulation of boid flocking that I built Spring 2015 for Dr. Keyser’s Physically Based Modeling (CSCE-649) class at Texas A&M using three.js and vanilla Javascript.

In the demo below, you can use the WASD keys to move around. You’ll notice three flocks (red, blue, and green) along with white agitator boids that change position. The spinning cubes are just for added affect and the boids should generally avoid them.

Demo

Code Graveyard

[Permalink]

This is a collection of ancient programs I wrote during high school. Most of these were found either on old thumb drives or on my Dropbox. Select programs have a short description.