Bluetooth Build Button, Part 5: the software

Now that v1.0 is out of the way (Part 4: v1.0 complete), we can spend some time using it day-to-day and build incremental improvements.

Goals

I’ve made it through a week with the build button as a daily driver of launching tests, and it works great! The battery life is surprisingly sufficient and has lasted over several workdays without a charge.

workstation

However, there are two issues I’m hoping to address via software.

The first is that the button indiscriminately enters rake spec/n when pressed via an iTerm binding. Even though I’ve been doing a lot of work in Rails projects, I also work in projects that are node.js or even Rails + webpacker, where I need to run test suites for both Ruby and Javascript. We simply need a better way to be smart about what gets run.

The second is that inside of Vim, there are multiple contexts I may want to trigger for test run – run the current spec, run all the specs in the current file, or run the entire test suite. I’ve been using the button to run the current spec, and falling back to key commands to execute the other cases. I want the button to handle all of this!

Bash

I know that the iTerm binding is too removed from the execution context to remain. Defining this as a key binding in iTerm means that it enters this text whether I’m in vim or a bash prompt or anywhere else. So let’s delete it:

iterm binding

Something that occurred to me is that being smart about the project and what test suite to run is really hard, if not impossible to get right. Instead, let’s start by making this purely customizable and expanding to be smart only if we must.

The simple idea is, let’s create a dotfile per project directory that contains the command we want to run. Then we can build a launcher that will run what’s in the dotfile, then we can bind to the launcher.

So, an example of the dotfile (I called it .button):

# Contents of .button:
rake spec

And our button-launcher script, written entirely in shell script. How easy was that?

#!/bin/bash

FILE='.button'
if [ -f $FILE ]; then
  while read p; do
    eval $p
  done < $FILE
else
  echo "No .button file found in current directory!"
fi

We can symlink it into our path at /usr/local/bin/:

sudo ln -s <full path>/button-launcher /usr/local/bin/button-launcher

As it turns out, bash has a builtin bind command that will at least let us keep it in the shell and out of vim. Thinking ahead to broader compatibility a little, I also checked other shells to make sure they had some equivalent command.

So, in our .bash-profile:

bind -x '"\e6":"button-launcher"'

You’ll notice we changed the keybinding. As it turns out, it’s a little challenging to find an available keybinding that works in terminal and vim and doesn’t overlap with anything else. In this case, we’ve moved to CTRL+ALT+6, and shortly will also use CTRL+ALT+7 and CTRL+ALT+8 for other commands.

vim-test

Inside of Vim / NeoVim, I use vim-test to launch my tests, which does a nice job of being context aware.

Let’s start by binding the button to run the test nearest to the cursor. In ~/.vimrc:

nmap <silent> <M-6> :TestNearest<CR> 

Extra contexts

Okay! Now the button runs whatever command I want in bash, and still launches the nearest test in vim-test. Let’s add some additional contexts.

My thought is to be able to hold the button just a little longer to increase the scope of the tests you run. So, quick push is current test, 1s hold is current file, 2s hold is test suite (for example).

In our firmware:

void onButtonRelease(unsigned long holdDuration) {
	unsigned long stageLength = 1000000 / 3;
	if(holdDuration < stageLength) {
		bleKeyboard.sendStage1Key();
	} else if(holdDuration < stageLength*2) {
		bleKeyboard.sendStage2Key();
	} else {
		bleKeyboard.sendStage3Key();
	}
}

These will trigger CTRL+ALT=6 through 8, respectively.

Let’s wire these up in our .vimrc:

nmap <silent> <M-6> :TestNearest<CR> 
nmap <silent> <M-7> :TestFile<CR>
nmap <silent> <M-8> :TestSuite<CR>

For now, let’s make sure in the shell that no matter how long we hold the button for, it fires our launcher:

bind -x '"\e6":"button-launcher"'
bind -x '"\e7":"button-launcher"'
bind -x '"\e8":"button-launcher"'

Now we can fire off different scopes of test runs, although we have no feedback as to how long we need to hold the button. I think we can start looking forward to using the NeoPixel ring as a feedback mechanism.

Scope Creep

Some things we haven’t addressed yet:

A diversion

A welcome diversion from actual progress: during a hack night, a buddy was playing with a robot arm and we combined projects to create a robot that would perpetually run it’s own program using the button:



What’s next

Every day I’m opening the button up to toggle the power switch, so it’s now my #1 priority to get that accessible on the outside of the button. Next: lights and a power switch